ocean-brain 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/package.json +3 -2
  2. package/server/client/dist/assets/{ArrowRight.es-C4zlc54Z.js → ArrowRight.es-CbA8ey6a.js} +1 -1
  3. package/server/client/dist/assets/{Calendar-COrN6Hjv.js → Calendar-CgWgr1BV.js} +1 -1
  4. package/server/client/dist/assets/{Callout-BK0K9E74.js → Callout-DKruIT0f.js} +1 -1
  5. package/server/client/dist/assets/{Copy.es-zm8f_XXX.js → Copy.es-DFbgpaNJ.js} +1 -1
  6. package/server/client/dist/assets/{Graph-BH1fzhue.js → Graph-kCltGRlY.js} +1 -1
  7. package/server/client/dist/assets/{Image-KXuHOG4b.js → Image-7W0DZwMo.js} +1 -1
  8. package/server/client/dist/assets/{Image.es-BKahRbIm.js → Image.es-5pXLx9TU.js} +1 -1
  9. package/server/client/dist/assets/Note-BW6Np_5g.js +20 -0
  10. package/server/client/dist/assets/{Plus.es-BqeC8T8h.js → Plus.es-B0sg64VJ.js} +1 -1
  11. package/server/client/dist/assets/{Reminders-BYG5rz1M.js → Reminders-C10fHQcl.js} +1 -1
  12. package/server/client/dist/assets/{Search-DBWsIMDP.js → Search-tf2nV4v0.js} +1 -1
  13. package/server/client/dist/assets/{SurfaceCard-CNSQfYfM.js → SurfaceCard-pydtvHfu.js} +1 -1
  14. package/server/client/dist/assets/{Tag-5aCfSO8Y.js → Tag-uCoYbISy.js} +1 -1
  15. package/server/client/dist/assets/{TagNotes-BPZjN-6l.js → TagNotes-CqOcQiRw.js} +1 -1
  16. package/server/client/dist/assets/{Trash.es-CRQkTnXZ.js → Trash.es-w32Goxfa.js} +1 -1
  17. package/server/client/dist/assets/{ViewNotes-DGLNF96x.js → ViewNotes-slPXXoOY.js} +1 -1
  18. package/server/client/dist/assets/{ViewSectionDialog-CzskgmMF.js → ViewSectionDialog-brTrsF6G.js} +1 -1
  19. package/server/client/dist/assets/{Views-DR5jT23G.js → Views-B_wsb-XJ.js} +1 -1
  20. package/server/client/dist/assets/{graph-vendor-UTvF2NS_.js → graph-vendor-BAF95C0g.js} +1 -1
  21. package/server/client/dist/assets/{image.api-8dlMwzMd.js → image.api-CYsjXiMr.js} +1 -1
  22. package/server/client/dist/assets/{index-VHp17GQX.js → index-BR05aSXr.js} +4 -4
  23. package/server/client/dist/assets/{index-DGIAh3ZY.js → index-D83QbOcw.js} +1 -1
  24. package/server/client/dist/assets/{index-BEUqDuAZ.css → index-DPI6YoDt.css} +1 -1
  25. package/server/client/dist/assets/{manage-image-RqtWKas2.js → manage-image-BYIhWu56.js} +1 -1
  26. package/server/client/dist/assets/{manage-image-detail-BTSIbLlQ.js → manage-image-detail-6KgmQvbN.js} +1 -1
  27. package/server/client/dist/assets/{mcp-EDurbggR.js → mcp-Dd98N9Hq.js} +2 -2
  28. package/server/client/dist/assets/{mcp-admin-adapter-DmGUo5G8.js → mcp-admin-adapter-DOgBsYpl.js} +1 -1
  29. package/server/client/dist/assets/note-core-CMGUaW1v.js +176 -0
  30. package/server/client/dist/assets/note-vendor-Hoz9Q4-4.js +54 -0
  31. package/server/client/dist/assets/{placeholder-DeKgSBnC.js → placeholder-siHGg9aK.js} +4 -4
  32. package/server/client/dist/assets/{properties-BjrZvOYn.js → properties-CyVRKgcT.js} +1 -1
  33. package/server/client/dist/assets/{tag.api-Dl8DQd0m.js → tag.api-CwmC8crB.js} +1 -1
  34. package/server/client/dist/assets/{trash-C5ntddLu.js → trash-wojQBK9Z.js} +1 -1
  35. package/server/client/dist/assets/{useReminderMutate-CoXXqJY2.js → useReminderMutate-DuVKxpBh.js} +1 -1
  36. package/server/client/dist/index.html +5 -5
  37. package/server/dist/app.js +0 -1
  38. package/server/dist/features/auth/http/api.js +0 -1
  39. package/server/dist/features/auth/http/login-page.js +272 -5
  40. package/server/dist/features/auth/http/pages.js +20 -3
  41. package/server/dist/features/auth/service.js +3 -2
  42. package/server/dist/features/cache/graphql/cache.field.resolver.js +0 -1
  43. package/server/dist/features/cache/graphql/cache.mutation.resolver.js +0 -1
  44. package/server/dist/features/cache/graphql/cache.query.resolver.js +0 -1
  45. package/server/dist/features/cache/graphql/cache.resolver.js +0 -1
  46. package/server/dist/features/cache/graphql/cache.type-defs.js +0 -1
  47. package/server/dist/features/cache/graphql/index.js +0 -1
  48. package/server/dist/features/image/graphql/image.field.resolver.js +0 -1
  49. package/server/dist/features/image/graphql/image.mutation.resolver.js +0 -1
  50. package/server/dist/features/image/graphql/image.query.resolver.js +0 -1
  51. package/server/dist/features/image/graphql/image.resolver.js +0 -1
  52. package/server/dist/features/image/graphql/image.type-defs.js +0 -1
  53. package/server/dist/features/image/graphql/index.js +0 -1
  54. package/server/dist/features/image/http/upload.js +0 -1
  55. package/server/dist/features/image/services/delete.js +0 -1
  56. package/server/dist/features/image/services/upload.js +0 -1
  57. package/server/dist/features/image/services/validation.js +0 -1
  58. package/server/dist/features/mcp-admin/http/handlers.js +0 -1
  59. package/server/dist/features/mcp-admin/service.js +0 -1
  60. package/server/dist/features/note/graphql/index.js +0 -1
  61. package/server/dist/features/note/graphql/note.field.resolver.js +0 -1
  62. package/server/dist/features/note/graphql/note.mutation.resolver.js +0 -1
  63. package/server/dist/features/note/graphql/note.query.resolver.js +0 -1
  64. package/server/dist/features/note/graphql/note.resolver.js +0 -1
  65. package/server/dist/features/note/graphql/note.type-defs.js +0 -1
  66. package/server/dist/features/note/http/mcp.js +0 -1
  67. package/server/dist/features/note/services/authoring.js +0 -1
  68. package/server/dist/features/note/services/cleanup.js +0 -1
  69. package/server/dist/features/note/services/content-blocks.js +0 -1
  70. package/server/dist/features/note/services/markdown-intent-write.js +0 -1
  71. package/server/dist/features/note/services/markdown-patch.js +0 -1
  72. package/server/dist/features/note/services/properties.js +0 -1
  73. package/server/dist/features/note/services/search-backfill.js +0 -1
  74. package/server/dist/features/note/services/search.js +0 -1
  75. package/server/dist/features/note/services/snapshot.js +0 -1
  76. package/server/dist/features/note/services/tag-filter.js +0 -1
  77. package/server/dist/features/note/services/trash.js +0 -1
  78. package/server/dist/features/note/services/write-conflict.js +0 -1
  79. package/server/dist/features/note/services/write.js +0 -1
  80. package/server/dist/features/placeholder/graphql/index.js +0 -1
  81. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +0 -1
  82. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +0 -1
  83. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +0 -1
  84. package/server/dist/features/placeholder/graphql/placeholder.resolver.js +0 -1
  85. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +0 -1
  86. package/server/dist/features/reminder/graphql/index.js +0 -1
  87. package/server/dist/features/reminder/graphql/reminder.field.resolver.js +0 -1
  88. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +0 -1
  89. package/server/dist/features/reminder/graphql/reminder.query.resolver.js +0 -1
  90. package/server/dist/features/reminder/graphql/reminder.resolver.js +0 -1
  91. package/server/dist/features/reminder/graphql/reminder.type-defs.js +0 -1
  92. package/server/dist/features/tag/graphql/index.js +0 -1
  93. package/server/dist/features/tag/graphql/tag.field.resolver.js +0 -1
  94. package/server/dist/features/tag/graphql/tag.mutation.resolver.js +0 -1
  95. package/server/dist/features/tag/graphql/tag.query.resolver.js +0 -1
  96. package/server/dist/features/tag/graphql/tag.resolver.js +0 -1
  97. package/server/dist/features/tag/graphql/tag.type-defs.js +0 -1
  98. package/server/dist/features/tag/http/mcp.js +0 -1
  99. package/server/dist/features/tag/services/organization.js +0 -1
  100. package/server/dist/features/view/graphql/index.js +0 -1
  101. package/server/dist/features/view/graphql/view.mutation.resolver.js +0 -1
  102. package/server/dist/features/view/graphql/view.query.resolver.js +0 -1
  103. package/server/dist/features/view/graphql/view.resolver.js +0 -1
  104. package/server/dist/features/view/graphql/view.type-defs.js +0 -1
  105. package/server/dist/features/view/services/workspace.js +0 -1
  106. package/server/dist/main.js +0 -1
  107. package/server/dist/models.js +0 -1
  108. package/server/dist/modules/app-version.js +0 -1
  109. package/server/dist/modules/auth-guard.js +0 -1
  110. package/server/dist/modules/auth-mode.js +0 -1
  111. package/server/dist/modules/auth-redirect.js +0 -1
  112. package/server/dist/modules/blocknote-markdown-editor.js +53 -0
  113. package/server/dist/modules/blocknote-tree.js +0 -1
  114. package/server/dist/modules/blocknote.js +10 -4
  115. package/server/dist/modules/data-maintenance.js +0 -1
  116. package/server/dist/modules/error-handler.js +0 -1
  117. package/server/dist/modules/graphql.js +0 -1
  118. package/server/dist/modules/logger.js +0 -1
  119. package/server/dist/modules/mcp-auth.js +0 -1
  120. package/server/dist/modules/mcp-token.js +0 -1
  121. package/server/dist/modules/prisma-runtime.js +0 -1
  122. package/server/dist/modules/rate-limit.js +0 -1
  123. package/server/dist/modules/recovery-retention.js +0 -1
  124. package/server/dist/modules/server-events-handler.js +0 -1
  125. package/server/dist/modules/server-events.js +0 -1
  126. package/server/dist/modules/session-store.js +0 -1
  127. package/server/dist/modules/use-async.js +0 -1
  128. package/server/dist/paths.js +0 -1
  129. package/server/dist/routes/api.js +0 -1
  130. package/server/dist/routes/auth-pages.js +0 -1
  131. package/server/dist/routes/client.js +0 -1
  132. package/server/dist/routes/graphql.js +0 -1
  133. package/server/dist/routes/index.js +0 -1
  134. package/server/dist/routes/mcp.js +0 -1
  135. package/server/dist/schema/index.js +0 -1
  136. package/server/dist/start.js +0 -1
  137. package/server/dist/types/index.js +0 -1
  138. package/server/dist/types/input.js +0 -1
  139. package/server/client/dist/assets/Note-C143cKKw.js +0 -12
  140. package/server/client/dist/assets/note-core-BklwyZHk.js +0 -180
  141. package/server/client/dist/assets/note-vendor-DkYKUmLo.js +0 -54
  142. package/server/client/dist/fonts/Pretendard-Bold.woff +0 -0
  143. package/server/client/dist/fonts/Pretendard-Regular.woff +0 -0
  144. package/server/dist/app.js.map +0 -1
  145. package/server/dist/features/auth/http/api.js.map +0 -1
  146. package/server/dist/features/auth/http/login-page.js.map +0 -1
  147. package/server/dist/features/auth/http/pages.js.map +0 -1
  148. package/server/dist/features/auth/service.js.map +0 -1
  149. package/server/dist/features/cache/graphql/cache.field.resolver.js.map +0 -1
  150. package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +0 -1
  151. package/server/dist/features/cache/graphql/cache.query.resolver.js.map +0 -1
  152. package/server/dist/features/cache/graphql/cache.resolver.js.map +0 -1
  153. package/server/dist/features/cache/graphql/cache.type-defs.js.map +0 -1
  154. package/server/dist/features/cache/graphql/index.js.map +0 -1
  155. package/server/dist/features/image/graphql/image.field.resolver.js.map +0 -1
  156. package/server/dist/features/image/graphql/image.mutation.resolver.js.map +0 -1
  157. package/server/dist/features/image/graphql/image.query.resolver.js.map +0 -1
  158. package/server/dist/features/image/graphql/image.resolver.js.map +0 -1
  159. package/server/dist/features/image/graphql/image.type-defs.js.map +0 -1
  160. package/server/dist/features/image/graphql/index.js.map +0 -1
  161. package/server/dist/features/image/http/upload.js.map +0 -1
  162. package/server/dist/features/image/services/delete.js.map +0 -1
  163. package/server/dist/features/image/services/upload.js.map +0 -1
  164. package/server/dist/features/image/services/validation.js.map +0 -1
  165. package/server/dist/features/mcp-admin/http/handlers.js.map +0 -1
  166. package/server/dist/features/mcp-admin/service.js.map +0 -1
  167. package/server/dist/features/note/graphql/index.js.map +0 -1
  168. package/server/dist/features/note/graphql/note.field.resolver.js.map +0 -1
  169. package/server/dist/features/note/graphql/note.mutation.resolver.js.map +0 -1
  170. package/server/dist/features/note/graphql/note.query.resolver.js.map +0 -1
  171. package/server/dist/features/note/graphql/note.resolver.js.map +0 -1
  172. package/server/dist/features/note/graphql/note.type-defs.js.map +0 -1
  173. package/server/dist/features/note/http/mcp.js.map +0 -1
  174. package/server/dist/features/note/services/authoring.js.map +0 -1
  175. package/server/dist/features/note/services/cleanup.js.map +0 -1
  176. package/server/dist/features/note/services/content-blocks.js.map +0 -1
  177. package/server/dist/features/note/services/markdown-intent-write.js.map +0 -1
  178. package/server/dist/features/note/services/markdown-patch.js.map +0 -1
  179. package/server/dist/features/note/services/properties.js.map +0 -1
  180. package/server/dist/features/note/services/search-backfill.js.map +0 -1
  181. package/server/dist/features/note/services/search.js.map +0 -1
  182. package/server/dist/features/note/services/snapshot.js.map +0 -1
  183. package/server/dist/features/note/services/tag-filter.js.map +0 -1
  184. package/server/dist/features/note/services/trash.js.map +0 -1
  185. package/server/dist/features/note/services/write-conflict.js.map +0 -1
  186. package/server/dist/features/note/services/write.js.map +0 -1
  187. package/server/dist/features/placeholder/graphql/index.js.map +0 -1
  188. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +0 -1
  189. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +0 -1
  190. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +0 -1
  191. package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +0 -1
  192. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +0 -1
  193. package/server/dist/features/reminder/graphql/index.js.map +0 -1
  194. package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +0 -1
  195. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +0 -1
  196. package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +0 -1
  197. package/server/dist/features/reminder/graphql/reminder.resolver.js.map +0 -1
  198. package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +0 -1
  199. package/server/dist/features/tag/graphql/index.js.map +0 -1
  200. package/server/dist/features/tag/graphql/tag.field.resolver.js.map +0 -1
  201. package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +0 -1
  202. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +0 -1
  203. package/server/dist/features/tag/graphql/tag.resolver.js.map +0 -1
  204. package/server/dist/features/tag/graphql/tag.type-defs.js.map +0 -1
  205. package/server/dist/features/tag/http/mcp.js.map +0 -1
  206. package/server/dist/features/tag/services/organization.js.map +0 -1
  207. package/server/dist/features/view/graphql/index.js.map +0 -1
  208. package/server/dist/features/view/graphql/view.mutation.resolver.js.map +0 -1
  209. package/server/dist/features/view/graphql/view.query.resolver.js.map +0 -1
  210. package/server/dist/features/view/graphql/view.resolver.js.map +0 -1
  211. package/server/dist/features/view/graphql/view.type-defs.js.map +0 -1
  212. package/server/dist/features/view/services/workspace.js.map +0 -1
  213. package/server/dist/main.js.map +0 -1
  214. package/server/dist/models.js.map +0 -1
  215. package/server/dist/modules/app-version.js.map +0 -1
  216. package/server/dist/modules/auth-guard.js.map +0 -1
  217. package/server/dist/modules/auth-mode.js.map +0 -1
  218. package/server/dist/modules/auth-redirect.js.map +0 -1
  219. package/server/dist/modules/blocknote-tree.js.map +0 -1
  220. package/server/dist/modules/blocknote.js.map +0 -1
  221. package/server/dist/modules/data-maintenance.js.map +0 -1
  222. package/server/dist/modules/error-handler.js.map +0 -1
  223. package/server/dist/modules/graphql.js.map +0 -1
  224. package/server/dist/modules/logger.js.map +0 -1
  225. package/server/dist/modules/mcp-auth.js.map +0 -1
  226. package/server/dist/modules/mcp-token.js.map +0 -1
  227. package/server/dist/modules/prisma-runtime.js.map +0 -1
  228. package/server/dist/modules/rate-limit.js.map +0 -1
  229. package/server/dist/modules/recovery-retention.js.map +0 -1
  230. package/server/dist/modules/server-events-handler.js.map +0 -1
  231. package/server/dist/modules/server-events.js.map +0 -1
  232. package/server/dist/modules/session-store.js.map +0 -1
  233. package/server/dist/modules/use-async.js.map +0 -1
  234. package/server/dist/paths.js.map +0 -1
  235. package/server/dist/routes/api.js.map +0 -1
  236. package/server/dist/routes/auth-pages.js.map +0 -1
  237. package/server/dist/routes/client.js.map +0 -1
  238. package/server/dist/routes/graphql.js.map +0 -1
  239. package/server/dist/routes/index.js.map +0 -1
  240. package/server/dist/routes/mcp.js.map +0 -1
  241. package/server/dist/schema/index.js.map +0 -1
  242. package/server/dist/start.js.map +0 -1
  243. package/server/dist/types/index.js.map +0 -1
  244. package/server/dist/types/input.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/app.ts"],"sourcesContent":["import express from 'express';\nimport { createMcpAdminService, type McpAdminService } from './features/mcp-admin/service.js';\nimport { purgeExpiredNoteSnapshots } from './features/note/services/snapshot.js';\nimport { purgeExpiredTrashedNotes } from './features/note/services/trash.js';\nimport { createSessionMiddleware } from './modules/auth-guard.js';\nimport type { AuthConfig } from './modules/auth-mode.js';\nimport { createErrorHandler } from './modules/error-handler.js';\nimport logger from './modules/logger.js';\nimport { createApiRouter, createAuthPagesRouter, createClientRouter, createGraphqlRouter } from './routes/index.js';\n\nexport const createApp = (authConfig: AuthConfig) => {\n const mcpAdminService = createMcpAdminService();\n return createAppWithMcpAuth(authConfig, mcpAdminService);\n};\n\nexport const createAppWithMcpAuth = (authConfig: AuthConfig, mcpAdminService: McpAdminService) => {\n const app = express();\n app.locals.authConfig = authConfig;\n\n void Promise.all([purgeExpiredNoteSnapshots(), purgeExpiredTrashedNotes()]).catch((error) => {\n const message = error instanceof Error ? error.message : 'Unknown recovery cleanup error';\n process.stderr.write(`[recovery] Startup cleanup failed: ${message}\\n`);\n });\n\n app.use(logger)\n .use(createSessionMiddleware(authConfig))\n .use(express.urlencoded({ extended: false }))\n .use(express.json({ limit: '50mb' }))\n .use('/api', createApiRouter(authConfig, mcpAdminService))\n .use(createAuthPagesRouter(authConfig))\n .use('/graphql', createGraphqlRouter(authConfig, mcpAdminService))\n .use(createClientRouter(authConfig))\n .use(createErrorHandler());\n\n return app;\n};\n\nexport default createApp;\n"],"mappings":"AAAA,OAAO,aAAa;AACpB,SAAS,6BAAmD;AAC5D,SAAS,iCAAiC;AAC1C,SAAS,gCAAgC;AACzC,SAAS,+BAA+B;AAExC,SAAS,0BAA0B;AACnC,OAAO,YAAY;AACnB,SAAS,iBAAiB,uBAAuB,oBAAoB,2BAA2B;AAEzF,MAAM,YAAY,CAAC,eAA2B;AACjD,QAAM,kBAAkB,sBAAsB;AAC9C,SAAO,qBAAqB,YAAY,eAAe;AAC3D;AAEO,MAAM,uBAAuB,CAAC,YAAwB,oBAAqC;AAC9F,QAAM,MAAM,QAAQ;AACpB,MAAI,OAAO,aAAa;AAExB,OAAK,QAAQ,IAAI,CAAC,0BAA0B,GAAG,yBAAyB,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU;AACzF,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,YAAQ,OAAO,MAAM,sCAAsC,OAAO;AAAA,CAAI;AAAA,EAC1E,CAAC;AAED,MAAI,IAAI,MAAM,EACT,IAAI,wBAAwB,UAAU,CAAC,EACvC,IAAI,QAAQ,WAAW,EAAE,UAAU,MAAM,CAAC,CAAC,EAC3C,IAAI,QAAQ,KAAK,EAAE,OAAO,OAAO,CAAC,CAAC,EACnC,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,IAAI,sBAAsB,UAAU,CAAC,EACrC,IAAI,YAAY,oBAAoB,YAAY,eAAe,CAAC,EAChE,IAAI,mBAAmB,UAAU,CAAC,EAClC,IAAI,mBAAmB,CAAC;AAE7B,SAAO;AACX;AAEA,IAAO,cAAQ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/auth/http/api.ts"],"sourcesContent":["import { buildAuthSessionResponse } from '@baejino/auth';\nimport type { AuthConfig } from '~/modules/auth-mode.js';\nimport { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport {\n assertPasswordLoginAvailable,\n buildSessionResponse,\n compareSharedSecret,\n destroySession,\n refreshCsrfToken,\n regenerateSession,\n setSessionStatusHeaders,\n} from '../service.js';\n\nexport const createLoginHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n const expectedPassword = assertPasswordLoginAvailable(authConfig);\n const password = typeof req.body?.password === 'string' ? req.body.password : '';\n\n if (!password || !compareSharedSecret(expectedPassword, password)) {\n throw createAppError(401, 'UNAUTHORIZED', 'Invalid password');\n }\n\n await regenerateSession(req);\n req.session.authenticated = true;\n refreshCsrfToken(req);\n\n res.status(200).json(buildSessionResponse(authConfig, req)).end();\n };\n};\n\nexport const createLogoutHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode === 'password') {\n await destroySession(req);\n }\n\n res.status(200).json(buildAuthSessionResponse(authConfig, false)).end();\n };\n};\n\nexport const createSessionStatusHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n setSessionStatusHeaders(res);\n res.status(200).json(buildSessionResponse(authConfig, req)).end();\n };\n};\n"],"mappings":"AAAA,SAAS,gCAAgC;AAEzC,SAAS,sBAAsB;AAE/B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEA,MAAM,qBAAqB,CAAC,eAAuC;AACtE,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,mBAAmB,6BAA6B,UAAU;AAChE,UAAM,WAAW,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,KAAK,WAAW;AAE9E,QAAI,CAAC,YAAY,CAAC,oBAAoB,kBAAkB,QAAQ,GAAG;AAC/D,YAAM,eAAe,KAAK,gBAAgB,kBAAkB;AAAA,IAChE;AAEA,UAAM,kBAAkB,GAAG;AAC3B,QAAI,QAAQ,gBAAgB;AAC5B,qBAAiB,GAAG;AAEpB,QAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,YAAY,GAAG,CAAC,EAAE,IAAI;AAAA,EACpE;AACJ;AAEO,MAAM,sBAAsB,CAAC,eAAuC;AACvE,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,WAAW,SAAS,YAAY;AAChC,YAAM,eAAe,GAAG;AAAA,IAC5B;AAEA,QAAI,OAAO,GAAG,EAAE,KAAK,yBAAyB,YAAY,KAAK,CAAC,EAAE,IAAI;AAAA,EAC1E;AACJ;AAEO,MAAM,6BAA6B,CAAC,eAAuC;AAC9E,SAAO,OAAO,KAAK,QAAQ;AACvB,4BAAwB,GAAG;AAC3B,QAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,YAAY,GAAG,CAAC,EAAE,IAAI;AAAA,EACpE;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/auth/http/login-page.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\n\ndeclare const __LOGIN_PAGE_TEMPLATE__: string | undefined;\n\nconst LOGIN_ERROR_TOKEN = '<!-- OCEAN_BRAIN_LOGIN_ERROR -->';\nconst NEXT_PATH_TOKEN = 'OCEAN_BRAIN_NEXT_PATH';\n\nconst readLoginPageTemplate = () => {\n if (typeof __LOGIN_PAGE_TEMPLATE__ === 'string') {\n return __LOGIN_PAGE_TEMPLATE__;\n }\n\n return readFileSync(new URL('./login-page.html', import.meta.url), 'utf8');\n};\n\nconst LOGIN_PAGE_TEMPLATE = readLoginPageTemplate();\n\nconst escapeHtml = (value: string) =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;');\n\ninterface LoginPageParams {\n nextPath: string;\n errorMessage?: string;\n csrfToken?: string;\n}\n\nconst renderLoginError = (errorMessage?: string) => {\n if (!errorMessage) {\n return '';\n }\n\n return `<div class=\"error\" role=\"alert\">${escapeHtml(errorMessage)}</div>`;\n};\n\nconst renderCsrfInput = (csrfToken?: string) => {\n if (!csrfToken) {\n return '';\n }\n\n return `<input type=\"hidden\" name=\"_csrf\" value=\"${escapeHtml(csrfToken)}\" />`;\n};\n\nconst renderLoginTemplate = (values: { errorBlock: string; nextPath: string; csrfInput: string }) => {\n return LOGIN_PAGE_TEMPLATE.replace(LOGIN_ERROR_TOKEN, values.errorBlock)\n .replace(NEXT_PATH_TOKEN, values.nextPath)\n .replace('<!-- OCEAN_BRAIN_CSRF_INPUT -->', values.csrfInput);\n};\n\nexport const renderLoginPage = ({ nextPath, errorMessage, csrfToken }: LoginPageParams) =>\n renderLoginTemplate({\n errorBlock: renderLoginError(errorMessage),\n nextPath: escapeHtml(nextPath),\n csrfInput: renderCsrfInput(csrfToken),\n });\n"],"mappings":"AAAA,SAAS,oBAAoB;AAI7B,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAExB,MAAM,wBAAwB,MAAM;AAChC,MAAI,MAA6C;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,aAAa,IAAI,IAAI,qBAAqB,YAAY,GAAG,GAAG,MAAM;AAC7E;AAEA,MAAM,sBAAsB,sBAAsB;AAElD,MAAM,aAAa,CAAC,UAChB,MACK,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,OAAO;AAQhC,MAAM,mBAAmB,CAAC,iBAA0B;AAChD,MAAI,CAAC,cAAc;AACf,WAAO;AAAA,EACX;AAEA,SAAO,mCAAmC,WAAW,YAAY,CAAC;AACtE;AAEA,MAAM,kBAAkB,CAAC,cAAuB;AAC5C,MAAI,CAAC,WAAW;AACZ,WAAO;AAAA,EACX;AAEA,SAAO,4CAA4C,WAAW,SAAS,CAAC;AAC5E;AAEA,MAAM,sBAAsB,CAAC,WAAwE;AACjG,SAAO,oBAAoB,QAAQ,mBAAmB,OAAO,UAAU,EAClE,QAAQ,iBAAiB,OAAO,QAAQ,EACxC,QAAQ,mCAAmC,OAAO,SAAS;AACpE;AAEO,MAAM,kBAAkB,CAAC,EAAE,UAAU,cAAc,UAAU,MAChE,oBAAoB;AAAA,EAChB,YAAY,iBAAiB,YAAY;AAAA,EACzC,UAAU,WAAW,QAAQ;AAAA,EAC7B,WAAW,gBAAgB,SAAS;AACxC,CAAC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/auth/http/pages.ts"],"sourcesContent":["import type { AuthConfig } from '~/modules/auth-mode.js';\nimport type { Controller } from '~/types/index.js';\nimport {\n compareSharedSecret,\n destroySession,\n refreshCsrfToken,\n regenerateSession,\n sanitizeRedirectPath,\n} from '../service.js';\nimport { renderLoginPage } from './login-page.js';\n\nexport const createLoginPageHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode !== 'password' || req.session?.authenticated) {\n res.redirect(303, sanitizeRedirectPath(req.query.next));\n return;\n }\n\n const nextPath = sanitizeRedirectPath(req.query.next);\n res.status(200)\n .type('html')\n .send(renderLoginPage({ nextPath, csrfToken: res.locals._csrf }))\n .end();\n };\n};\n\nexport const createLoginPageSubmitHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n const nextPath = sanitizeRedirectPath(req.body?.next);\n\n if (authConfig.mode !== 'password' || !authConfig.password) {\n res.redirect(303, nextPath);\n return;\n }\n\n const password = typeof req.body?.password === 'string' ? req.body.password : '';\n\n if (!password || !compareSharedSecret(authConfig.password, password)) {\n res.status(401)\n .type('html')\n .send(\n renderLoginPage({\n nextPath,\n errorMessage: 'Invalid password',\n csrfToken: res.locals._csrf,\n }),\n )\n .end();\n return;\n }\n\n await regenerateSession(req);\n req.session.authenticated = true;\n refreshCsrfToken(req);\n\n res.redirect(303, nextPath);\n };\n};\n\nexport const createLogoutPageHandler = (authConfig: AuthConfig): Controller => {\n return async (req, res) => {\n if (authConfig.mode === 'password') {\n await destroySession(req);\n res.redirect(303, '/login');\n return;\n }\n\n res.redirect(303, '/');\n };\n};\n"],"mappings":"AAEA;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,uBAAuB;AAEzB,MAAM,yBAAyB,CAAC,eAAuC;AAC1E,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,WAAW,SAAS,cAAc,IAAI,SAAS,eAAe;AAC9D,UAAI,SAAS,KAAK,qBAAqB,IAAI,MAAM,IAAI,CAAC;AACtD;AAAA,IACJ;AAEA,UAAM,WAAW,qBAAqB,IAAI,MAAM,IAAI;AACpD,QAAI,OAAO,GAAG,EACT,KAAK,MAAM,EACX,KAAK,gBAAgB,EAAE,UAAU,WAAW,IAAI,OAAO,MAAM,CAAC,CAAC,EAC/D,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,+BAA+B,CAAC,eAAuC;AAChF,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,WAAW,qBAAqB,IAAI,MAAM,IAAI;AAEpD,QAAI,WAAW,SAAS,cAAc,CAAC,WAAW,UAAU;AACxD,UAAI,SAAS,KAAK,QAAQ;AAC1B;AAAA,IACJ;AAEA,UAAM,WAAW,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,KAAK,WAAW;AAE9E,QAAI,CAAC,YAAY,CAAC,oBAAoB,WAAW,UAAU,QAAQ,GAAG;AAClE,UAAI,OAAO,GAAG,EACT,KAAK,MAAM,EACX;AAAA,QACG,gBAAgB;AAAA,UACZ;AAAA,UACA,cAAc;AAAA,UACd,WAAW,IAAI,OAAO;AAAA,QAC1B,CAAC;AAAA,MACL,EACC,IAAI;AACT;AAAA,IACJ;AAEA,UAAM,kBAAkB,GAAG;AAC3B,QAAI,QAAQ,gBAAgB;AAC5B,qBAAiB,GAAG;AAEpB,QAAI,SAAS,KAAK,QAAQ;AAAA,EAC9B;AACJ;AAEO,MAAM,0BAA0B,CAAC,eAAuC;AAC3E,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,WAAW,SAAS,YAAY;AAChC,YAAM,eAAe,GAAG;AACxB,UAAI,SAAS,KAAK,QAAQ;AAC1B;AAAA,IACJ;AAEA,QAAI,SAAS,KAAK,GAAG;AAAA,EACzB;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/features/auth/service.ts"],"sourcesContent":["import { buildAuthSessionResponse } from '@baejino/auth';\nimport { compareSharedSecret as compareCommonSharedSecret } from '@baejino/auth/crypto';\nimport crypto from 'crypto';\nimport type { Request, Response } from 'express';\nimport type { AuthConfig } from '~/modules/auth-mode.js';\nimport { sanitizeRedirectPath } from '~/modules/auth-redirect.js';\nimport { createAppError } from '~/modules/error-handler.js';\n\nexport const AUTH_SESSION_GENERATION_HEADER = 'X-Ocean-Brain-Session-Generation';\nconst AUTH_SESSION_GENERATION = crypto.randomUUID();\n\nexport const createPasswordHash = async (password: string) => {\n const salt = crypto.randomBytes(16).toString('hex');\n const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');\n return `${salt}:${hash}`;\n};\n\nexport const comparePassword = async (password: string, storedHash: string) => {\n const [salt, hash] = storedHash.split(':');\n const newHash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');\n return hash === newHash;\n};\n\nexport const compareSharedSecret = compareCommonSharedSecret;\n\nexport const buildSessionResponse = (authConfig: AuthConfig, req: Request) =>\n buildAuthSessionResponse(authConfig, Boolean(req.session?.authenticated));\n\nexport const setSessionStatusHeaders = (res: Response) => {\n res.set(AUTH_SESSION_GENERATION_HEADER, AUTH_SESSION_GENERATION);\n res.set('Cache-Control', 'no-store');\n};\n\nexport const refreshCsrfToken = (req: Request) => {\n const requestWithCsrf = req as Request & { csrfToken?: () => string };\n requestWithCsrf.csrfToken?.();\n};\n\nexport const assertPasswordLoginAvailable = (authConfig: AuthConfig) => {\n if (authConfig.mode !== 'password') {\n throw createAppError(409, 'AUTH_OPEN_MODE', 'Login is unavailable while auth mode is open.');\n }\n\n return authConfig.password;\n};\n\nexport const regenerateSession = async (req: Request) => {\n await new Promise<void>((resolve, reject) => {\n req.session.regenerate((error) => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve();\n });\n });\n};\n\nexport const destroySession = async (req: Request) => {\n if (!req.session) {\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n req.session.destroy((error) => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve();\n });\n });\n};\n\nexport { sanitizeRedirectPath };\n"],"mappings":"AAAA,SAAS,gCAAgC;AACzC,SAAS,uBAAuB,iCAAiC;AACjE,OAAO,YAAY;AAGnB,SAAS,4BAA4B;AACrC,SAAS,sBAAsB;AAExB,MAAM,iCAAiC;AAC9C,MAAM,0BAA0B,OAAO,WAAW;AAE3C,MAAM,qBAAqB,OAAO,aAAqB;AAC1D,QAAM,OAAO,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAClD,QAAM,OAAO,OAAO,WAAW,UAAU,MAAM,KAAM,IAAI,QAAQ,EAAE,SAAS,KAAK;AACjF,SAAO,GAAG,IAAI,IAAI,IAAI;AAC1B;AAEO,MAAM,kBAAkB,OAAO,UAAkB,eAAuB;AAC3E,QAAM,CAAC,MAAM,IAAI,IAAI,WAAW,MAAM,GAAG;AACzC,QAAM,UAAU,OAAO,WAAW,UAAU,MAAM,KAAM,IAAI,QAAQ,EAAE,SAAS,KAAK;AACpF,SAAO,SAAS;AACpB;AAEO,MAAM,sBAAsB;AAE5B,MAAM,uBAAuB,CAAC,YAAwB,QACzD,yBAAyB,YAAY,QAAQ,IAAI,SAAS,aAAa,CAAC;AAErE,MAAM,0BAA0B,CAAC,QAAkB;AACtD,MAAI,IAAI,gCAAgC,uBAAuB;AAC/D,MAAI,IAAI,iBAAiB,UAAU;AACvC;AAEO,MAAM,mBAAmB,CAAC,QAAiB;AAC9C,QAAM,kBAAkB;AACxB,kBAAgB,YAAY;AAChC;AAEO,MAAM,+BAA+B,CAAC,eAA2B;AACpE,MAAI,WAAW,SAAS,YAAY;AAChC,UAAM,eAAe,KAAK,kBAAkB,+CAA+C;AAAA,EAC/F;AAEA,SAAO,WAAW;AACtB;AAEO,MAAM,oBAAoB,OAAO,QAAiB;AACrD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AACzC,QAAI,QAAQ,WAAW,CAAC,UAAU;AAC9B,UAAI,OAAO;AACP,eAAO,KAAK;AACZ;AAAA,MACJ;AAEA,cAAQ;AAAA,IACZ,CAAC;AAAA,EACL,CAAC;AACL;AAEO,MAAM,iBAAiB,OAAO,QAAiB;AAClD,MAAI,CAAC,IAAI,SAAS;AACd;AAAA,EACJ;AAEA,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AACzC,QAAI,QAAQ,QAAQ,CAAC,UAAU;AAC3B,UAAI,OAAO;AACP,eAAO,KAAK;AACZ;AAAA,MACJ;AAEA,cAAQ;AAAA,IACZ,CAAC;AAAA,EACL,CAAC;AACL;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/cache/graphql/cache.field.resolver.ts"],"sourcesContent":["export const cacheFieldResolvers = {};\n"],"mappings":"AAAO,MAAM,sBAAsB,CAAC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/cache/graphql/cache.mutation.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport models, { type Cache } from '~/models.js';\n\ntype CacheRecord = Pick<Cache, 'id' | 'key' | 'value'>;\ntype CacheMutationInput = Pick<Cache, 'key' | 'value'>;\n\ntype CacheMutationService = {\n findCacheByKey: (key: string) => Promise<CacheRecord | null>;\n createCache: (input: CacheMutationInput) => Promise<CacheRecord>;\n updateCache: (input: CacheMutationInput) => Promise<CacheRecord>;\n deleteCache: (key: string) => Promise<void>;\n};\n\nconst createCacheMutationService = (): CacheMutationService => ({\n findCacheByKey: (key) => models.cache.findFirst({ where: { key } }),\n createCache: ({ key, value }) =>\n models.cache.create({\n data: {\n key,\n value,\n },\n }),\n updateCache: ({ key, value }) =>\n models.cache.update({\n where: { key },\n data: { value },\n }),\n deleteCache: async (key) => {\n await models.cache.delete({ where: { key } });\n },\n});\n\nexport const createSetCacheMutationResolver = (service: CacheMutationService = createCacheMutationService()) => {\n return async (_: unknown, { key, value }: CacheMutationInput) => {\n const decodedValue = decodeURIComponent(value);\n const existingCache = await service.findCacheByKey(key);\n\n if (existingCache) {\n return service.updateCache({\n key,\n value: decodedValue,\n });\n }\n\n return service.createCache({\n key,\n value: decodedValue,\n });\n };\n};\n\nexport const createDeleteCacheMutationResolver = (service: CacheMutationService = createCacheMutationService()) => {\n return async (_: unknown, { key }: Pick<Cache, 'key'>) => {\n await service.deleteCache(key);\n return true;\n };\n};\n\nexport const cacheMutationResolvers: NonNullable<IResolvers['Mutation']> = {\n setCache: createSetCacheMutationResolver(),\n deleteCache: createDeleteCacheMutationResolver(),\n};\n"],"mappings":"AAEA,OAAO,YAA4B;AAYnC,MAAM,6BAA6B,OAA6B;AAAA,EAC5D,gBAAgB,CAAC,QAAQ,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA,EAClE,aAAa,CAAC,EAAE,KAAK,MAAM,MACvB,OAAO,MAAM,OAAO;AAAA,IAChB,MAAM;AAAA,MACF;AAAA,MACA;AAAA,IACJ;AAAA,EACJ,CAAC;AAAA,EACL,aAAa,CAAC,EAAE,KAAK,MAAM,MACvB,OAAO,MAAM,OAAO;AAAA,IAChB,OAAO,EAAE,IAAI;AAAA,IACb,MAAM,EAAE,MAAM;AAAA,EAClB,CAAC;AAAA,EACL,aAAa,OAAO,QAAQ;AACxB,UAAM,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA,EAChD;AACJ;AAEO,MAAM,iCAAiC,CAAC,UAAgC,2BAA2B,MAAM;AAC5G,SAAO,OAAO,GAAY,EAAE,KAAK,MAAM,MAA0B;AAC7D,UAAM,eAAe,mBAAmB,KAAK;AAC7C,UAAM,gBAAgB,MAAM,QAAQ,eAAe,GAAG;AAEtD,QAAI,eAAe;AACf,aAAO,QAAQ,YAAY;AAAA,QACvB;AAAA,QACA,OAAO;AAAA,MACX,CAAC;AAAA,IACL;AAEA,WAAO,QAAQ,YAAY;AAAA,MACvB;AAAA,MACA,OAAO;AAAA,IACX,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,oCAAoC,CAAC,UAAgC,2BAA2B,MAAM;AAC/G,SAAO,OAAO,GAAY,EAAE,IAAI,MAA0B;AACtD,UAAM,QAAQ,YAAY,GAAG;AAC7B,WAAO;AAAA,EACX;AACJ;AAEO,MAAM,yBAA8D;AAAA,EACvE,UAAU,+BAA+B;AAAA,EACzC,aAAa,kCAAkC;AACnD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/cache/graphql/cache.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport models, { type Cache } from '~/models.js';\n\ntype CacheRecord = Pick<Cache, 'id' | 'key' | 'value'>;\n\ntype CacheQueryService = {\n findCaches: () => Promise<CacheRecord[]>;\n findCacheByKey: (key: string) => Promise<CacheRecord | null>;\n};\n\nconst createCacheQueryService = (): CacheQueryService => ({\n findCaches: () => models.cache.findMany(),\n findCacheByKey: (key) => models.cache.findUnique({ where: { key } }),\n});\n\nexport const createAllCachesQueryResolver = (service: CacheQueryService = createCacheQueryService()) => {\n return async () => service.findCaches();\n};\n\nexport const createCacheQueryResolver = (service: CacheQueryService = createCacheQueryService()) => {\n return async (_: unknown, { key }: { key: string }) => service.findCacheByKey(key);\n};\n\nexport const cacheQueryResolvers: NonNullable<IResolvers['Query']> = {\n allCaches: createAllCachesQueryResolver(),\n cache: createCacheQueryResolver(),\n};\n"],"mappings":"AAEA,OAAO,YAA4B;AASnC,MAAM,0BAA0B,OAA0B;AAAA,EACtD,YAAY,MAAM,OAAO,MAAM,SAAS;AAAA,EACxC,gBAAgB,CAAC,QAAQ,OAAO,MAAM,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACvE;AAEO,MAAM,+BAA+B,CAAC,UAA6B,wBAAwB,MAAM;AACpG,SAAO,YAAY,QAAQ,WAAW;AAC1C;AAEO,MAAM,2BAA2B,CAAC,UAA6B,wBAAwB,MAAM;AAChG,SAAO,OAAO,GAAY,EAAE,IAAI,MAAuB,QAAQ,eAAe,GAAG;AACrF;AAEO,MAAM,sBAAwD;AAAA,EACjE,WAAW,6BAA6B;AAAA,EACxC,OAAO,yBAAyB;AACpC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/cache/graphql/cache.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport { cacheFieldResolvers } from './cache.field.resolver.js';\nimport { cacheMutationResolvers } from './cache.mutation.resolver.js';\nimport { cacheQueryResolvers } from './cache.query.resolver.js';\n\nexport const cacheResolvers: IResolvers = {\n Cache: cacheFieldResolvers,\n Query: cacheQueryResolvers,\n Mutation: cacheMutationResolvers,\n};\n"],"mappings":"AAEA,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAE7B,MAAM,iBAA6B;AAAA,EACtC,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AACd;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/cache/graphql/cache.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const cacheType = gql`\n type Cache {\n id: ID!\n key: String!\n value: String!\n }\n`;\n\nexport const cacheQuery = gql`\n type Query {\n allCaches: [Cache!]!\n cache(key: String!): Cache!\n }\n`;\n\nexport const cacheMutation = gql`\n type Mutation {\n setCache(key: String, value: String): Cache!\n deleteCache(key: String!): Boolean!\n }\n`;\n\nexport const cacheTypeDefs = `\n ${cacheType}\n ${cacheQuery}\n ${cacheMutation}\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlB,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnB,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB,MAAM,gBAAgB;AAAA,MACvB,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/cache/graphql/index.ts"],"sourcesContent":["export { cacheFieldResolvers } from './cache.field.resolver.js';\nexport {\n cacheMutationResolvers,\n createDeleteCacheMutationResolver,\n createSetCacheMutationResolver,\n} from './cache.mutation.resolver.js';\nexport { cacheQueryResolvers, createAllCachesQueryResolver, createCacheQueryResolver } from './cache.query.resolver.js';\nexport { cacheResolvers } from './cache.resolver.js';\nexport { cacheTypeDefs } from './cache.type-defs.js';\n"],"mappings":"AAAA,SAAS,2BAA2B;AACpC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,qBAAqB,8BAA8B,gCAAgC;AAC5F,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/graphql/image.field.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport models, { type Image } from '~/models.js';\n\ntype ImageFieldResolvers = NonNullable<IResolvers['Image']>;\n\ntype CountImageReferences = (url: string) => Promise<number>;\n\nexport const createImageReferenceCountFieldResolver = (\n countReferences: CountImageReferences = async (url) => models.note.count({ where: { content: { contains: url } } }),\n) => {\n return async (image: Image) => {\n return countReferences(image.url);\n };\n};\n\nexport const imageFieldResolvers: ImageFieldResolvers = {\n referenceCount: createImageReferenceCountFieldResolver(),\n};\n"],"mappings":"AACA,OAAO,YAA4B;AAM5B,MAAM,yCAAyC,CAClD,kBAAwC,OAAO,QAAQ,OAAO,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,IAAI,EAAE,EAAE,CAAC,MACjH;AACD,SAAO,OAAO,UAAiB;AAC3B,WAAO,gBAAgB,MAAM,GAAG;AAAA,EACpC;AACJ;AAEO,MAAM,sBAA2C;AAAA,EACpD,gBAAgB,uCAAuC;AAC3D;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/graphql/image.mutation.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { deleteImageById } from '../services/delete.js';\n\ntype ImageMutationResolvers = NonNullable<IResolvers['Mutation']>;\n\ntype DeleteImage = (id: number) => Promise<boolean>;\n\nexport const createDeleteImageMutationResolver = (deleteImage: DeleteImage = deleteImageById) => {\n return async (_: unknown, { id }: { id: string | number }) => {\n try {\n return await deleteImage(Number(id));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[image] Delete failed: ${message}\\n`);\n return false;\n }\n };\n};\n\nexport const imageMutationResolvers: ImageMutationResolvers = {\n deleteImage: createDeleteImageMutationResolver(),\n};\n"],"mappings":"AACA,SAAS,uBAAuB;AAMzB,MAAM,oCAAoC,CAAC,cAA2B,oBAAoB;AAC7F,SAAO,OAAO,GAAY,EAAE,GAAG,MAA+B;AAC1D,QAAI;AACA,aAAO,MAAM,YAAY,OAAO,EAAE,CAAC;AAAA,IACvC,SAAS,OAAO;AACZ,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,OAAO,MAAM,0BAA0B,OAAO;AAAA,CAAI;AAC1D,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEO,MAAM,yBAAiD;AAAA,EAC1D,aAAa,kCAAkC;AACnD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/graphql/image.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport models from '~/models.js';\nimport type { Pagination } from '~/types/index.js';\n\ntype ImageQueryResolvers = NonNullable<IResolvers['Query']>;\n\ninterface ImageQueryDeps {\n countImages: () => Promise<number>;\n findImageById: (id: number) => Promise<unknown>;\n findImages: (input: { orderBy: { createdAt: 'desc' }; skip: number; take: number }) => Promise<unknown[]>;\n}\n\nexport const createAllImagesQueryResolver = (\n deps: ImageQueryDeps = {\n countImages: async () => models.image.count(),\n findImageById: async (id) => models.image.findFirst({ where: { id } }),\n findImages: async (input) => models.image.findMany(input),\n },\n) => {\n return async (\n _: unknown,\n {\n pagination,\n }: {\n pagination: Pagination;\n },\n ) => {\n const [totalCount, images] = await Promise.all([\n deps.countImages(),\n deps.findImages({\n skip: pagination.offset,\n take: pagination.limit,\n orderBy: { createdAt: 'desc' },\n }),\n ]);\n\n return {\n totalCount,\n images,\n };\n };\n};\n\nexport const createImageQueryResolver = (\n deps: Pick<ImageQueryDeps, 'findImageById'> = {\n findImageById: async (id) => models.image.findFirst({ where: { id } }),\n },\n) => {\n return async (_: unknown, { id }: { id: string | number }) => {\n return deps.findImageById(Number(id));\n };\n};\n\nexport const imageQueryResolvers: ImageQueryResolvers = {\n allImages: createAllImagesQueryResolver(),\n image: createImageQueryResolver(),\n};\n"],"mappings":"AACA,OAAO,YAAY;AAWZ,MAAM,+BAA+B,CACxC,OAAuB;AAAA,EACnB,aAAa,YAAY,OAAO,MAAM,MAAM;AAAA,EAC5C,eAAe,OAAO,OAAO,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAAA,EACrE,YAAY,OAAO,UAAU,OAAO,MAAM,SAAS,KAAK;AAC5D,MACC;AACD,SAAO,OACH,GACA;AAAA,IACI;AAAA,EACJ,MAGC;AACD,UAAM,CAAC,YAAY,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3C,KAAK,YAAY;AAAA,MACjB,KAAK,WAAW;AAAA,QACZ,MAAM,WAAW;AAAA,QACjB,MAAM,WAAW;AAAA,QACjB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAAA,IACL,CAAC;AAED,WAAO;AAAA,MACH;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AACJ;AAEO,MAAM,2BAA2B,CACpC,OAA8C;AAAA,EAC1C,eAAe,OAAO,OAAO,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AACzE,MACC;AACD,SAAO,OAAO,GAAY,EAAE,GAAG,MAA+B;AAC1D,WAAO,KAAK,cAAc,OAAO,EAAE,CAAC;AAAA,EACxC;AACJ;AAEO,MAAM,sBAA2C;AAAA,EACpD,WAAW,6BAA6B;AAAA,EACxC,OAAO,yBAAyB;AACpC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/graphql/image.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { imageFieldResolvers } from './image.field.resolver.js';\nimport { imageMutationResolvers } from './image.mutation.resolver.js';\nimport { imageQueryResolvers } from './image.query.resolver.js';\n\nexport const imageResolvers: IResolvers = {\n Query: imageQueryResolvers,\n Mutation: imageMutationResolvers,\n Image: imageFieldResolvers,\n};\n"],"mappings":"AACA,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAE7B,MAAM,iBAA6B;AAAA,EACtC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AACX;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/graphql/image.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const imageTypeDefs = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n type Image {\n id: ID!\n url: String!\n referenceCount: Int!\n }\n\n type Images {\n totalCount: Int!\n images: [Image!]!\n }\n\n type Query {\n allImages(pagination: PaginationInput): Images!\n image(id: ID!): Image!\n }\n\n type Mutation {\n deleteImage(id: ID!): Boolean!\n }\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,gBAAgB;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;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/graphql/index.ts"],"sourcesContent":["export { imageFieldResolvers } from './image.field.resolver.js';\nexport { createDeleteImageMutationResolver, imageMutationResolvers } from './image.mutation.resolver.js';\nexport { createAllImagesQueryResolver, createImageQueryResolver, imageQueryResolvers } from './image.query.resolver.js';\nexport { imageResolvers } from './image.resolver.js';\nexport { imageTypeDefs } from './image.type-defs.js';\n"],"mappings":"AAAA,SAAS,2BAA2B;AACpC,SAAS,mCAAmC,8BAA8B;AAC1E,SAAS,8BAA8B,0BAA0B,2BAA2B;AAC5F,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/http/upload.ts"],"sourcesContent":["import { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport { type PersistedImageSummary, type PersistImageInput, persistUploadedImage } from '../services/upload.js';\nimport { ImageValidationError, normalizeImageContentType, validateImagePayload } from '../services/validation.js';\n\ntype PersistImage = (input: PersistImageInput) => Promise<PersistedImageSummary>;\n\nconst IMAGE_DATA_URL_PATTERN = /^data:(image\\/[a-z0-9.+-]+);base64,([A-Za-z0-9+/]+={0,2})$/i;\n\nconst createUploadResponse = (image: PersistedImageSummary) => {\n return {\n id: image.id,\n url: image.url,\n };\n};\n\nconst parseImageDataUrl = (image: string) => {\n const match = IMAGE_DATA_URL_PATTERN.exec(image);\n\n if (!match || !match[2] || match[2].length % 4 !== 0) {\n return null;\n }\n\n return {\n base64: match[2],\n contentType: normalizeImageContentType(match[1]),\n };\n};\n\nconst createUploadValidationError = (error: ImageValidationError) => {\n if (error.code === 'IMAGE_UNSUPPORTED_CONTENT_TYPE') {\n return createAppError(415, 'IMAGE_UPLOAD_UNSUPPORTED_TYPE', 'Uploaded image content type is not supported.');\n }\n\n return createAppError(400, 'INVALID_IMAGE_UPLOAD', 'Uploaded image content is invalid.');\n};\n\nexport const createUploadImageHandler = (persistImage: PersistImage = persistUploadedImage): Controller => {\n return async (req, res) => {\n const { image } = req.body ?? {};\n\n if (typeof image !== 'string') {\n throw createAppError(400, 'INVALID_IMAGE_UPLOAD', 'No image uploaded');\n }\n\n const parsedImage = parseImageDataUrl(image);\n\n if (!parsedImage) {\n throw createAppError(400, 'INVALID_IMAGE_UPLOAD', 'No image uploaded');\n }\n\n let validatedImage;\n\n try {\n validatedImage = validateImagePayload({\n buffer: Buffer.from(parsedImage.base64, 'base64'),\n contentType: parsedImage.contentType,\n });\n } catch (error) {\n if (error instanceof ImageValidationError) {\n throw createUploadValidationError(error);\n }\n\n throw error;\n }\n\n const uploadedImage = await persistImage({\n buffer: validatedImage.buffer,\n extension: validatedImage.extension,\n });\n\n res.status(200).json(createUploadResponse(uploadedImage)).end();\n };\n};\n"],"mappings":"AAAA,SAAS,sBAAsB;AAE/B,SAA6D,4BAA4B;AACzF,SAAS,sBAAsB,2BAA2B,4BAA4B;AAItF,MAAM,yBAAyB;AAE/B,MAAM,uBAAuB,CAAC,UAAiC;AAC3D,SAAO;AAAA,IACH,IAAI,MAAM;AAAA,IACV,KAAK,MAAM;AAAA,EACf;AACJ;AAEA,MAAM,oBAAoB,CAAC,UAAkB;AACzC,QAAM,QAAQ,uBAAuB,KAAK,KAAK;AAE/C,MAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,EAAE,SAAS,MAAM,GAAG;AAClD,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,QAAQ,MAAM,CAAC;AAAA,IACf,aAAa,0BAA0B,MAAM,CAAC,CAAC;AAAA,EACnD;AACJ;AAEA,MAAM,8BAA8B,CAAC,UAAgC;AACjE,MAAI,MAAM,SAAS,kCAAkC;AACjD,WAAO,eAAe,KAAK,iCAAiC,+CAA+C;AAAA,EAC/G;AAEA,SAAO,eAAe,KAAK,wBAAwB,oCAAoC;AAC3F;AAEO,MAAM,2BAA2B,CAAC,eAA6B,yBAAqC;AACvG,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,MAAM,IAAI,IAAI,QAAQ,CAAC;AAE/B,QAAI,OAAO,UAAU,UAAU;AAC3B,YAAM,eAAe,KAAK,wBAAwB,mBAAmB;AAAA,IACzE;AAEA,UAAM,cAAc,kBAAkB,KAAK;AAE3C,QAAI,CAAC,aAAa;AACd,YAAM,eAAe,KAAK,wBAAwB,mBAAmB;AAAA,IACzE;AAEA,QAAI;AAEJ,QAAI;AACA,uBAAiB,qBAAqB;AAAA,QAClC,QAAQ,OAAO,KAAK,YAAY,QAAQ,QAAQ;AAAA,QAChD,aAAa,YAAY;AAAA,MAC7B,CAAC;AAAA,IACL,SAAS,OAAO;AACZ,UAAI,iBAAiB,sBAAsB;AACvC,cAAM,4BAA4B,KAAK;AAAA,MAC3C;AAEA,YAAM;AAAA,IACV;AAEA,UAAM,gBAAgB,MAAM,aAAa;AAAA,MACrC,QAAQ,eAAe;AAAA,MACvB,WAAW,eAAe;AAAA,IAC9B,CAAC;AAED,QAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,aAAa,CAAC,EAAE,IAAI;AAAA,EAClE;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/services/delete.ts"],"sourcesContent":["import fs from 'fs';\nimport fsPromises from 'fs/promises';\nimport path from 'path';\n\nimport models from '~/models.js';\nimport { paths } from '~/paths.js';\n\ninterface DeleteImageRecord {\n id: number;\n url: string;\n}\n\ninterface ImageDeleteDeps {\n deleteImageRecord: (id: number) => Promise<void>;\n fileExists: (filePath: string) => boolean;\n findImageById: (id: number) => Promise<DeleteImageRecord | null>;\n removeFile: (filePath: string) => Promise<void>;\n resolveImagePath: (url: string) => string;\n}\n\nexport const resolveStoredImagePath = (url: string) => {\n return path.resolve(paths.imageDir, url.replace('/assets/images/', ''));\n};\n\nexport const createImageDeleteService = (deps: ImageDeleteDeps) => {\n return {\n deleteImageById: async (id: number) => {\n const image = await deps.findImageById(id);\n\n if (!image) {\n return false;\n }\n\n const imagePath = deps.resolveImagePath(image.url);\n\n if (deps.fileExists(imagePath)) {\n await deps.removeFile(imagePath);\n }\n\n await deps.deleteImageRecord(id);\n\n return true;\n },\n };\n};\n\nconst defaultImageDeleteService = createImageDeleteService({\n deleteImageRecord: async (id) => {\n await models.image.delete({ where: { id } });\n },\n fileExists: (filePath) => fs.existsSync(filePath),\n findImageById: async (id) => {\n return models.image.findFirst({\n where: { id },\n select: {\n id: true,\n url: true,\n },\n });\n },\n removeFile: async (filePath) => {\n await fsPromises.unlink(filePath);\n },\n resolveImagePath: resolveStoredImagePath,\n});\n\nexport const deleteImageById = async (id: number) => {\n return defaultImageDeleteService.deleteImageById(id);\n};\n"],"mappings":"AAAA,OAAO,QAAQ;AACf,OAAO,gBAAgB;AACvB,OAAO,UAAU;AAEjB,OAAO,YAAY;AACnB,SAAS,aAAa;AAef,MAAM,yBAAyB,CAAC,QAAgB;AACnD,SAAO,KAAK,QAAQ,MAAM,UAAU,IAAI,QAAQ,mBAAmB,EAAE,CAAC;AAC1E;AAEO,MAAM,2BAA2B,CAAC,SAA0B;AAC/D,SAAO;AAAA,IACH,iBAAiB,OAAO,OAAe;AACnC,YAAM,QAAQ,MAAM,KAAK,cAAc,EAAE;AAEzC,UAAI,CAAC,OAAO;AACR,eAAO;AAAA,MACX;AAEA,YAAM,YAAY,KAAK,iBAAiB,MAAM,GAAG;AAEjD,UAAI,KAAK,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,WAAW,SAAS;AAAA,MACnC;AAEA,YAAM,KAAK,kBAAkB,EAAE;AAE/B,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEA,MAAM,4BAA4B,yBAAyB;AAAA,EACvD,mBAAmB,OAAO,OAAO;AAC7B,UAAM,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAAA,EAC/C;AAAA,EACA,YAAY,CAAC,aAAa,GAAG,WAAW,QAAQ;AAAA,EAChD,eAAe,OAAO,OAAO;AACzB,WAAO,OAAO,MAAM,UAAU;AAAA,MAC1B,OAAO,EAAE,GAAG;AAAA,MACZ,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,KAAK;AAAA,MACT;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,YAAY,OAAO,aAAa;AAC5B,UAAM,WAAW,OAAO,QAAQ;AAAA,EACpC;AAAA,EACA,kBAAkB;AACtB,CAAC;AAEM,MAAM,kBAAkB,OAAO,OAAe;AACjD,SAAO,0BAA0B,gBAAgB,EAAE;AACvD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/services/upload.ts"],"sourcesContent":["import crypto from 'crypto';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nimport models from '~/models.js';\nimport { paths } from '~/paths.js';\n\ninterface ImageRecord {\n id: number;\n url: string;\n}\n\ninterface ImageUploadDeps {\n createImage: (input: { hash: string; url: string }) => Promise<ImageRecord>;\n ensureDir: (dirPath: string) => Promise<void>;\n findImageByHash: (hash: string) => Promise<ImageRecord | null>;\n removeFile: (filePath: string) => Promise<void>;\n writeFile: (filePath: string, buffer: Buffer) => Promise<void>;\n}\n\nexport interface PersistImageInput {\n buffer: Buffer;\n extension: string;\n}\n\nexport interface PersistedImageSummary {\n id: number;\n url: string;\n}\n\nexport const hashImageBuffer = (buffer: Buffer) => {\n return crypto.createHash('sha512').update(buffer).digest('hex');\n};\n\nconst buildImageTarget = (hash: string, extension: string, now = new Date()) => {\n const normalizedExtension = extension.trim().toLowerCase().replace(/^\\./, '');\n const currentPath = [now.getFullYear().toString(), (now.getMonth() + 1).toString(), now.getDate().toString()];\n const targetDir = path.resolve(paths.imageDir, ...currentPath);\n const fileName = `${hash}.${normalizedExtension}`;\n\n return {\n targetDir,\n absolutePath: path.resolve(targetDir, fileName),\n url: `/assets/images/${currentPath.join('/')}/${fileName}`,\n };\n};\n\nconst isImageHashUniqueConflict = (error: unknown) => {\n if (typeof error !== 'object' || error === null || !('code' in error)) {\n return false;\n }\n\n if (error.code !== 'P2002') {\n return false;\n }\n\n if (!('meta' in error) || typeof error.meta !== 'object' || error.meta === null) {\n return true;\n }\n\n const rawTarget = 'target' in error.meta ? error.meta.target : undefined;\n const targets = Array.isArray(rawTarget)\n ? rawTarget.filter((value): value is string => typeof value === 'string')\n : typeof rawTarget === 'string'\n ? [rawTarget]\n : [];\n\n return targets.length === 0 || targets.includes('hash');\n};\n\nconst removeFileIfPresent = async (deps: Pick<ImageUploadDeps, 'removeFile'>, filePath: string) => {\n try {\n await deps.removeFile(filePath);\n } catch (error) {\n if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {\n return;\n }\n\n throw error;\n }\n};\n\nexport const createImageUploadService = (deps: ImageUploadDeps) => {\n return {\n persistImage: async (input: PersistImageInput): Promise<PersistedImageSummary> => {\n const hash = hashImageBuffer(input.buffer);\n const existingImage = await deps.findImageByHash(hash);\n\n if (existingImage) {\n return existingImage;\n }\n\n const target = buildImageTarget(hash, input.extension);\n\n await deps.ensureDir(target.targetDir);\n await deps.writeFile(target.absolutePath, input.buffer);\n\n try {\n return await deps.createImage({\n hash,\n url: target.url,\n });\n } catch (error) {\n await removeFileIfPresent(deps, target.absolutePath);\n\n if (isImageHashUniqueConflict(error)) {\n const conflictedImage = await deps.findImageByHash(hash);\n\n if (conflictedImage) {\n return conflictedImage;\n }\n }\n\n throw error;\n }\n },\n };\n};\n\nconst defaultImageUploadService = createImageUploadService({\n createImage: async (input) => {\n return models.image.create({\n data: input,\n select: {\n id: true,\n url: true,\n },\n });\n },\n ensureDir: async (dirPath) => {\n await fs.mkdir(dirPath, { recursive: true });\n },\n findImageByHash: async (hash) => {\n return models.image.findFirst({\n where: { hash },\n select: {\n id: true,\n url: true,\n },\n });\n },\n removeFile: async (filePath) => {\n await fs.unlink(filePath);\n },\n writeFile: async (filePath, buffer) => {\n await fs.writeFile(filePath, buffer);\n },\n});\n\nexport const persistUploadedImage = async (input: PersistImageInput) => {\n return defaultImageUploadService.persistImage(input);\n};\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,OAAO,YAAY;AACnB,SAAS,aAAa;AAyBf,MAAM,kBAAkB,CAAC,WAAmB;AAC/C,SAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAClE;AAEA,MAAM,mBAAmB,CAAC,MAAc,WAAmB,MAAM,oBAAI,KAAK,MAAM;AAC5E,QAAM,sBAAsB,UAAU,KAAK,EAAE,YAAY,EAAE,QAAQ,OAAO,EAAE;AAC5E,QAAM,cAAc,CAAC,IAAI,YAAY,EAAE,SAAS,IAAI,IAAI,SAAS,IAAI,GAAG,SAAS,GAAG,IAAI,QAAQ,EAAE,SAAS,CAAC;AAC5G,QAAM,YAAY,KAAK,QAAQ,MAAM,UAAU,GAAG,WAAW;AAC7D,QAAM,WAAW,GAAG,IAAI,IAAI,mBAAmB;AAE/C,SAAO;AAAA,IACH;AAAA,IACA,cAAc,KAAK,QAAQ,WAAW,QAAQ;AAAA,IAC9C,KAAK,kBAAkB,YAAY,KAAK,GAAG,CAAC,IAAI,QAAQ;AAAA,EAC5D;AACJ;AAEA,MAAM,4BAA4B,CAAC,UAAmB;AAClD,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,EAAE,UAAU,QAAQ;AACnE,WAAO;AAAA,EACX;AAEA,MAAI,MAAM,SAAS,SAAS;AACxB,WAAO;AAAA,EACX;AAEA,MAAI,EAAE,UAAU,UAAU,OAAO,MAAM,SAAS,YAAY,MAAM,SAAS,MAAM;AAC7E,WAAO;AAAA,EACX;AAEA,QAAM,YAAY,YAAY,MAAM,OAAO,MAAM,KAAK,SAAS;AAC/D,QAAM,UAAU,MAAM,QAAQ,SAAS,IACjC,UAAU,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IACtE,OAAO,cAAc,WACnB,CAAC,SAAS,IACV,CAAC;AAET,SAAO,QAAQ,WAAW,KAAK,QAAQ,SAAS,MAAM;AAC1D;AAEA,MAAM,sBAAsB,OAAO,MAA2C,aAAqB;AAC/F,MAAI;AACA,UAAM,KAAK,WAAW,QAAQ;AAAA,EAClC,SAAS,OAAO;AACZ,QAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,SAAS,MAAM,SAAS,UAAU;AAC3F;AAAA,IACJ;AAEA,UAAM;AAAA,EACV;AACJ;AAEO,MAAM,2BAA2B,CAAC,SAA0B;AAC/D,SAAO;AAAA,IACH,cAAc,OAAO,UAA6D;AAC9E,YAAM,OAAO,gBAAgB,MAAM,MAAM;AACzC,YAAM,gBAAgB,MAAM,KAAK,gBAAgB,IAAI;AAErD,UAAI,eAAe;AACf,eAAO;AAAA,MACX;AAEA,YAAM,SAAS,iBAAiB,MAAM,MAAM,SAAS;AAErD,YAAM,KAAK,UAAU,OAAO,SAAS;AACrC,YAAM,KAAK,UAAU,OAAO,cAAc,MAAM,MAAM;AAEtD,UAAI;AACA,eAAO,MAAM,KAAK,YAAY;AAAA,UAC1B;AAAA,UACA,KAAK,OAAO;AAAA,QAChB,CAAC;AAAA,MACL,SAAS,OAAO;AACZ,cAAM,oBAAoB,MAAM,OAAO,YAAY;AAEnD,YAAI,0BAA0B,KAAK,GAAG;AAClC,gBAAM,kBAAkB,MAAM,KAAK,gBAAgB,IAAI;AAEvD,cAAI,iBAAiB;AACjB,mBAAO;AAAA,UACX;AAAA,QACJ;AAEA,cAAM;AAAA,MACV;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,MAAM,4BAA4B,yBAAyB;AAAA,EACvD,aAAa,OAAO,UAAU;AAC1B,WAAO,OAAO,MAAM,OAAO;AAAA,MACvB,MAAM;AAAA,MACN,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,KAAK;AAAA,MACT;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,WAAW,OAAO,YAAY;AAC1B,UAAM,GAAG,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AAAA,EACA,iBAAiB,OAAO,SAAS;AAC7B,WAAO,OAAO,MAAM,UAAU;AAAA,MAC1B,OAAO,EAAE,KAAK;AAAA,MACd,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,KAAK;AAAA,MACT;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,YAAY,OAAO,aAAa;AAC5B,UAAM,GAAG,OAAO,QAAQ;AAAA,EAC5B;AAAA,EACA,WAAW,OAAO,UAAU,WAAW;AACnC,UAAM,GAAG,UAAU,UAAU,MAAM;AAAA,EACvC;AACJ,CAAC;AAEM,MAAM,uBAAuB,OAAO,UAA6B;AACpE,SAAO,0BAA0B,aAAa,KAAK;AACvD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/image/services/validation.ts"],"sourcesContent":["export type ImageValidationErrorCode = 'IMAGE_INVALID_CONTENT' | 'IMAGE_UNSUPPORTED_CONTENT_TYPE';\n\nexport class ImageValidationError extends Error {\n public readonly code: ImageValidationErrorCode;\n public readonly status: number;\n\n constructor(code: ImageValidationErrorCode, status: number, message: string) {\n super(message);\n this.name = 'ImageValidationError';\n this.code = code;\n this.status = status;\n }\n}\n\ninterface ValidateImagePayloadInput {\n buffer: Buffer;\n contentType: string;\n}\n\nexport interface ValidatedImagePayload {\n buffer: Buffer;\n contentType: string;\n extension: string;\n}\n\nconst SUPPORTED_IMAGE_TYPES = new Map([\n ['image/png', 'png'],\n ['image/jpeg', 'jpg'],\n ['image/gif', 'gif'],\n ['image/webp', 'webp'],\n ['image/bmp', 'bmp'],\n ['image/avif', 'avif'],\n]);\n\nconst startsWithBytes = (buffer: Buffer, bytes: number[]) => {\n return bytes.every((byte, index) => buffer[index] === byte);\n};\n\nconst readAscii = (buffer: Buffer, start: number, end: number) => {\n return buffer.subarray(start, end).toString('ascii');\n};\n\nconst hasPngSignature = (buffer: Buffer) => {\n return buffer.length >= 8 && startsWithBytes(buffer, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n};\n\nconst hasJpegSignature = (buffer: Buffer) => {\n return buffer.length >= 3 && startsWithBytes(buffer, [0xff, 0xd8, 0xff]);\n};\n\nconst hasGifSignature = (buffer: Buffer) => {\n return buffer.length >= 6 && ['GIF87a', 'GIF89a'].includes(readAscii(buffer, 0, 6));\n};\n\nconst hasWebpSignature = (buffer: Buffer) => {\n return buffer.length >= 12 && readAscii(buffer, 0, 4) === 'RIFF' && readAscii(buffer, 8, 12) === 'WEBP';\n};\n\nconst hasBmpSignature = (buffer: Buffer) => {\n return buffer.length >= 2 && readAscii(buffer, 0, 2) === 'BM';\n};\n\nconst hasAvifSignature = (buffer: Buffer) => {\n if (buffer.length < 16 || readAscii(buffer, 4, 8) !== 'ftyp') {\n return false;\n }\n\n const ftypBoxSize = buffer.readUInt32BE(0);\n\n if (ftypBoxSize !== 0 && (ftypBoxSize < 16 || ftypBoxSize > buffer.length)) {\n return false;\n }\n\n const isAvifBrand = (brand: string) => brand === 'avif' || brand === 'avis';\n const majorBrand = readAscii(buffer, 8, 12);\n\n if (isAvifBrand(majorBrand)) {\n return true;\n }\n\n const boxEnd = ftypBoxSize === 0 ? buffer.length : ftypBoxSize;\n\n for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {\n const brand = readAscii(buffer, offset, offset + 4);\n\n if (isAvifBrand(brand)) {\n return true;\n }\n }\n\n return false;\n};\n\nconst hasExpectedImageSignature = (contentType: string, buffer: Buffer) => {\n switch (contentType) {\n case 'image/png':\n return hasPngSignature(buffer);\n case 'image/jpeg':\n return hasJpegSignature(buffer);\n case 'image/gif':\n return hasGifSignature(buffer);\n case 'image/webp':\n return hasWebpSignature(buffer);\n case 'image/bmp':\n return hasBmpSignature(buffer);\n case 'image/avif':\n return hasAvifSignature(buffer);\n default:\n return false;\n }\n};\n\nexport const normalizeImageContentType = (contentType: string | null | undefined) => {\n return contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n};\n\nexport const getSupportedImageExtension = (contentType: string) => {\n return SUPPORTED_IMAGE_TYPES.get(normalizeImageContentType(contentType));\n};\n\nexport const validateImagePayload = ({\n buffer,\n contentType: rawContentType,\n}: ValidateImagePayloadInput): ValidatedImagePayload => {\n const contentType = normalizeImageContentType(rawContentType);\n const extension = SUPPORTED_IMAGE_TYPES.get(contentType);\n\n if (!extension) {\n throw new ImageValidationError('IMAGE_UNSUPPORTED_CONTENT_TYPE', 415, 'Image content type is not supported.');\n }\n\n const hasExpectedSignature = hasExpectedImageSignature(contentType, buffer);\n\n if (!hasExpectedSignature) {\n throw new ImageValidationError('IMAGE_INVALID_CONTENT', 415, 'Image content does not match the declared type.');\n }\n\n return {\n buffer,\n contentType,\n extension,\n };\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/mcp-admin/http/handlers.ts"],"sourcesContent":["import { getOceanBrainVersionInfo } from '~/modules/app-version.js';\nimport { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport { createMcpAdminService, type McpAdminService } from '../service.js';\n\ntype McpAdminControllerService = Pick<\n McpAdminService,\n 'getStatus' | 'setEnabled' | 'rotateToken' | 'revokeActiveToken'\n>;\n\nconst createMcpAdminStatusResponse = async (service: McpAdminControllerService) => {\n const status = await service.getStatus();\n\n return {\n ...status,\n server: getOceanBrainVersionInfo(),\n };\n};\n\nexport const createMcpAdminStatusHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (_req, res) => {\n const status = await createMcpAdminStatusResponse(service);\n res.status(200).json(status).end();\n };\n};\n\nexport const createMcpAdminSetEnabledHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (req, res) => {\n const enabled = req.body?.enabled;\n if (typeof enabled !== 'boolean') {\n throw createAppError(400, 'INVALID_MCP_ENABLED', 'enabled must be a boolean.');\n }\n\n await service.setEnabled(enabled);\n const status = await createMcpAdminStatusResponse(service);\n res.status(200).json(status).end();\n };\n};\n\nexport const createMcpAdminRotateTokenHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (_req, res) => {\n const result = await service.rotateToken();\n res.status(200)\n .json({\n token: result.token,\n message: 'Save this token now. It is shown only once.',\n })\n .end();\n };\n};\n\nexport const createMcpAdminRevokeTokenHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (_req, res) => {\n await service.revokeActiveToken();\n const status = await createMcpAdminStatusResponse(service);\n res.status(200).json(status).end();\n };\n};\n"],"mappings":"AAAA,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAE/B,SAAS,6BAAmD;AAO5D,MAAM,+BAA+B,OAAO,YAAuC;AAC/E,QAAM,SAAS,MAAM,QAAQ,UAAU;AAEvC,SAAO;AAAA,IACH,GAAG;AAAA,IACH,QAAQ,yBAAyB;AAAA,EACrC;AACJ;AAEO,MAAM,8BAA8B,CACvC,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,MAAM,QAAQ;AACxB,UAAM,SAAS,MAAM,6BAA6B,OAAO;AACzD,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,kCAAkC,CAC3C,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,UAAU,IAAI,MAAM;AAC1B,QAAI,OAAO,YAAY,WAAW;AAC9B,YAAM,eAAe,KAAK,uBAAuB,4BAA4B;AAAA,IACjF;AAEA,UAAM,QAAQ,WAAW,OAAO;AAChC,UAAM,SAAS,MAAM,6BAA6B,OAAO;AACzD,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,mCAAmC,CAC5C,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,MAAM,QAAQ;AACxB,UAAM,SAAS,MAAM,QAAQ,YAAY;AACzC,QAAI,OAAO,GAAG,EACT,KAAK;AAAA,MACF,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,IACb,CAAC,EACA,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,mCAAmC,CAC5C,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,MAAM,QAAQ;AACxB,UAAM,QAAQ,kBAAkB;AAChC,UAAM,SAAS,MAAM,6BAA6B,OAAO;AACzD,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/features/mcp-admin/service.ts"],"sourcesContent":["import models from '~/models.js';\nimport { issueMcpToken, verifyMcpToken } from '~/modules/mcp-token.js';\n\nconst MCP_ENABLED_CACHE_KEY = 'MCP_ENABLED';\n\nexport interface McpTokenSummary {\n id: string;\n createdAt: string;\n lastUsedAt: string | null;\n}\n\nexport interface McpAdminStatus {\n enabled: boolean;\n hasActiveToken: boolean;\n token: McpTokenSummary | null;\n}\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminService {\n getStatus: () => Promise<McpAdminStatus>;\n setEnabled: (enabled: boolean) => Promise<void>;\n rotateToken: () => Promise<{ token: string }>;\n revokeActiveToken: () => Promise<void>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\ninterface McpAdminDb {\n cache: {\n findUnique: (args: { where: { key: string } }) => Promise<{ value: string } | null>;\n upsert: (args: {\n where: { key: string };\n update: { value: string };\n create: { key: string; value: string };\n }) => Promise<unknown>;\n };\n mcpToken: {\n findFirst: (args: { where: { revokedAt: Date | null }; orderBy: { createdAt: 'desc' | 'asc' } }) => Promise<{\n id: number;\n tokenHash: string;\n createdAt: Date;\n lastUsedAt: Date | null;\n revokedAt: Date | null;\n } | null>;\n updateMany: (args: {\n where: { revokedAt: Date | null };\n data: { revokedAt: Date };\n }) => Promise<{ count: number }>;\n create: (args: { data: { tokenHash: string } }) => Promise<unknown>;\n update: (args: { where: { id: number }; data: { lastUsedAt: Date } }) => Promise<unknown>;\n };\n $transaction: <T>(callback: (tx: McpAdminDb) => Promise<T>) => Promise<T>;\n}\n\nconst toStatus = (\n enabled: boolean,\n activeToken: {\n id: number;\n createdAt: Date;\n lastUsedAt: Date | null;\n } | null,\n): McpAdminStatus => {\n if (!activeToken) {\n return {\n enabled,\n hasActiveToken: false,\n token: null,\n };\n }\n\n return {\n enabled,\n hasActiveToken: true,\n token: {\n id: String(activeToken.id),\n createdAt: activeToken.createdAt.toISOString(),\n lastUsedAt: activeToken.lastUsedAt ? activeToken.lastUsedAt.toISOString() : null,\n },\n };\n};\n\nexport const createMcpAdminService = (db: McpAdminDb = models as unknown as McpAdminDb): McpAdminService => {\n return {\n async getStatus() {\n const enabledCache = await db.cache.findUnique({ where: { key: MCP_ENABLED_CACHE_KEY } });\n const activeToken = await db.mcpToken.findFirst({\n where: { revokedAt: null },\n orderBy: { createdAt: 'desc' },\n });\n\n return toStatus(enabledCache?.value === 'true', activeToken);\n },\n async setEnabled(enabled) {\n await db.cache.upsert({\n where: { key: MCP_ENABLED_CACHE_KEY },\n update: { value: String(enabled) },\n create: {\n key: MCP_ENABLED_CACHE_KEY,\n value: String(enabled),\n },\n });\n },\n async rotateToken() {\n const now = new Date();\n const issued = issueMcpToken();\n\n await db.$transaction(async (transaction) => {\n await transaction.mcpToken.updateMany({\n where: { revokedAt: null },\n data: { revokedAt: now },\n });\n await transaction.mcpToken.create({ data: { tokenHash: issued.hash } });\n });\n\n return { token: issued.plaintext };\n },\n async revokeActiveToken() {\n await db.mcpToken.updateMany({\n where: { revokedAt: null },\n data: { revokedAt: new Date() },\n });\n },\n async validatePresentedToken(token) {\n const activeToken = await db.mcpToken.findFirst({\n where: { revokedAt: null },\n orderBy: { createdAt: 'desc' },\n });\n\n if (!activeToken) {\n return {\n ok: false,\n reason: 'not_configured',\n };\n }\n\n if (!verifyMcpToken(activeToken.tokenHash, token)) {\n return {\n ok: false,\n reason: 'forbidden',\n };\n }\n\n await db.mcpToken.update({\n where: { id: activeToken.id },\n data: { lastUsedAt: new Date() },\n });\n\n return { ok: true };\n },\n };\n};\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,SAAS,eAAe,sBAAsB;AAE9C,MAAM,wBAAwB;AAsD9B,MAAM,WAAW,CACb,SACA,gBAKiB;AACjB,MAAI,CAAC,aAAa;AACd,WAAO;AAAA,MACH;AAAA,MACA,gBAAgB;AAAA,MAChB,OAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,IACA,gBAAgB;AAAA,IAChB,OAAO;AAAA,MACH,IAAI,OAAO,YAAY,EAAE;AAAA,MACzB,WAAW,YAAY,UAAU,YAAY;AAAA,MAC7C,YAAY,YAAY,aAAa,YAAY,WAAW,YAAY,IAAI;AAAA,IAChF;AAAA,EACJ;AACJ;AAEO,MAAM,wBAAwB,CAAC,KAAiB,WAAqD;AACxG,SAAO;AAAA,IACH,MAAM,YAAY;AACd,YAAM,eAAe,MAAM,GAAG,MAAM,WAAW,EAAE,OAAO,EAAE,KAAK,sBAAsB,EAAE,CAAC;AACxF,YAAM,cAAc,MAAM,GAAG,SAAS,UAAU;AAAA,QAC5C,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAED,aAAO,SAAS,cAAc,UAAU,QAAQ,WAAW;AAAA,IAC/D;AAAA,IACA,MAAM,WAAW,SAAS;AACtB,YAAM,GAAG,MAAM,OAAO;AAAA,QAClB,OAAO,EAAE,KAAK,sBAAsB;AAAA,QACpC,QAAQ,EAAE,OAAO,OAAO,OAAO,EAAE;AAAA,QACjC,QAAQ;AAAA,UACJ,KAAK;AAAA,UACL,OAAO,OAAO,OAAO;AAAA,QACzB;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,IACA,MAAM,cAAc;AAChB,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,SAAS,cAAc;AAE7B,YAAM,GAAG,aAAa,OAAO,gBAAgB;AACzC,cAAM,YAAY,SAAS,WAAW;AAAA,UAClC,OAAO,EAAE,WAAW,KAAK;AAAA,UACzB,MAAM,EAAE,WAAW,IAAI;AAAA,QAC3B,CAAC;AACD,cAAM,YAAY,SAAS,OAAO,EAAE,MAAM,EAAE,WAAW,OAAO,KAAK,EAAE,CAAC;AAAA,MAC1E,CAAC;AAED,aAAO,EAAE,OAAO,OAAO,UAAU;AAAA,IACrC;AAAA,IACA,MAAM,oBAAoB;AACtB,YAAM,GAAG,SAAS,WAAW;AAAA,QACzB,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,MAAM,EAAE,WAAW,oBAAI,KAAK,EAAE;AAAA,MAClC,CAAC;AAAA,IACL;AAAA,IACA,MAAM,uBAAuB,OAAO;AAChC,YAAM,cAAc,MAAM,GAAG,SAAS,UAAU;AAAA,QAC5C,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAED,UAAI,CAAC,aAAa;AACd,eAAO;AAAA,UACH,IAAI;AAAA,UACJ,QAAQ;AAAA,QACZ;AAAA,MACJ;AAEA,UAAI,CAAC,eAAe,YAAY,WAAW,KAAK,GAAG;AAC/C,eAAO;AAAA,UACH,IAAI;AAAA,UACJ,QAAQ;AAAA,QACZ;AAAA,MACJ;AAEA,YAAM,GAAG,SAAS,OAAO;AAAA,QACrB,OAAO,EAAE,IAAI,YAAY,GAAG;AAAA,QAC5B,MAAM,EAAE,YAAY,oBAAI,KAAK,EAAE;AAAA,MACnC,CAAC;AAED,aAAO,EAAE,IAAI,KAAK;AAAA,IACtB;AAAA,EACJ;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/graphql/index.ts"],"sourcesContent":["export { createAllNotesQueryResolver, noteResolvers } from './note.resolver.js';\nexport { noteTypeDefs } from './note.type-defs.js';\n"],"mappings":"AAAA,SAAS,6BAA6B,qBAAqB;AAC3D,SAAS,oBAAoB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/graphql/note.field.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { listNoteProperties } from '~/features/note/services/properties.js';\nimport { renderNoteSnapshotContentAsMarkdown } from '~/features/note/services/snapshot.js';\nimport type { Note } from '~/models.js';\nimport models from '~/models.js';\n\ntype NoteFieldResolvers = NonNullable<IResolvers['Note']>;\ntype NoteSnapshotFieldResolvers = NonNullable<IResolvers['NoteSnapshot']>;\n\ninterface NoteSnapshotSource {\n contentAsMarkdown?: string;\n payload?: string;\n}\n\nexport const noteFieldResolvers: NoteFieldResolvers = {\n tags: async (note: Note) => models.tag.findMany({ where: { notes: { some: { id: note.id } } } }),\n properties: async (note: Note) => listNoteProperties(note.id),\n contentAsMarkdown: async (note: Note) => {\n const { blocksToMarkdown } = await import('~/modules/blocknote.js');\n return blocksToMarkdown(note.content);\n },\n};\n\nexport const noteSnapshotFieldResolvers: NoteSnapshotFieldResolvers = {\n contentAsMarkdown: async (snapshot: NoteSnapshotSource) => {\n if (typeof snapshot.contentAsMarkdown === 'string') {\n return snapshot.contentAsMarkdown;\n }\n\n if (typeof snapshot.payload === 'string') {\n return renderNoteSnapshotContentAsMarkdown(snapshot.payload);\n }\n\n return '';\n },\n};\n"],"mappings":"AACA,SAAS,0BAA0B;AACnC,SAAS,2CAA2C;AAEpD,OAAO,YAAY;AAUZ,MAAM,qBAAyC;AAAA,EAClD,MAAM,OAAO,SAAe,OAAO,IAAI,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC;AAAA,EAC/F,YAAY,OAAO,SAAe,mBAAmB,KAAK,EAAE;AAAA,EAC5D,mBAAmB,OAAO,SAAe;AACrC,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAwB;AAClE,WAAO,iBAAiB,KAAK,OAAO;AAAA,EACxC;AACJ;AAEO,MAAM,6BAAyD;AAAA,EAClE,mBAAmB,OAAO,aAAiC;AACvD,QAAI,OAAO,SAAS,sBAAsB,UAAU;AAChD,aAAO,SAAS;AAAA,IACpB;AAEA,QAAI,OAAO,SAAS,YAAY,UAAU;AACtC,aAAO,oCAAoC,SAAS,OAAO;AAAA,IAC/D;AAEA,WAAO;AAAA,EACX;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/graphql/note.mutation.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport type { Request } from 'express';\nimport { GraphQLError } from 'graphql';\nimport { extractBlocksByType, parseNoteContent } from '~/features/note/services/content-blocks.js';\nimport {\n createNotePropertyDefinition,\n deleteNotePropertyDefinition,\n InvalidNotePropertyInputError,\n type NotePropertiesPatchInput,\n type NotePropertyDefinitionInput,\n type NotePropertyDefinitionUpdateInput,\n NotePropertyDeleteConfirmationRequiredError,\n updateNotePropertiesWithVersionGuard,\n updateNotePropertyDefinition,\n} from '~/features/note/services/properties.js';\nimport { buildNoteSearchProjection } from '~/features/note/services/search.js';\nimport { createSnapshotMetaFromUserAgent, restoreNoteSnapshot } from '~/features/note/services/snapshot.js';\nimport { purgeTrashedNoteById, restoreTrashedNoteById, trashNoteById } from '~/features/note/services/trash.js';\nimport { updateNoteWithVersionGuard } from '~/features/note/services/write.js';\nimport {\n isInvalidNoteVersionError,\n isMissingNoteVersionError,\n isNoteVersionConflictError,\n} from '~/features/note/services/write-conflict.js';\nimport models from '~/models.js';\nimport type { NoteInput } from '~/types/index.js';\n\nconst PLACEHOLDER_PREFIX = '{%';\nconst PLACEHOLDER_SUFFIX = '%}';\n\nconst replacePlaceholders = async (content: string) => {\n const placeholders = content.matchAll(new RegExp(`${PLACEHOLDER_PREFIX}([^}]+)${PLACEHOLDER_SUFFIX}`, 'g'));\n const resolvedPlaceholders = await models.placeholder.findMany({\n select: {\n template: true,\n replacement: true,\n },\n where: { template: { in: Array.from(new Set(Array.from(placeholders, (match) => match[1]))) } },\n });\n\n let replacedContent = content;\n\n for (const placeholder of resolvedPlaceholders) {\n replacedContent = replacedContent.replace(\n new RegExp(`${PLACEHOLDER_PREFIX}${placeholder.template}${PLACEHOLDER_SUFFIX}`, 'g'),\n placeholder.replacement,\n );\n }\n\n return replacedContent;\n};\n\nconst getRequestUserAgent = (req?: Request) => {\n const userAgentHeader = req?.headers['user-agent'];\n return Array.isArray(userAgentHeader) ? userAgentHeader[0] : userAgentHeader;\n};\n\nconst toTagConnections = (blocks: Array<{ props?: { id?: string | number } }>) => {\n return blocks\n .map((block) => block.props?.id)\n .filter((id) => id !== undefined && id !== null && String(id).trim() !== '')\n .map((id) => Number(id))\n .filter(Number.isFinite)\n .map((id) => ({ id }));\n};\n\nconst parseRequiredNoteContent = (content: string) => {\n const parsedContent = parseNoteContent(content);\n\n if (!parsedContent) {\n throw new GraphQLError('Note content must be valid BlockNote JSON.', {\n extensions: {\n code: 'INVALID_NOTE_CONTENT',\n },\n });\n }\n\n return parsedContent;\n};\n\ntype NoteMutationResolvers = NonNullable<IResolvers['Mutation']>;\ntype CreateNoteInput = Required<Pick<NoteInput, 'title' | 'content'>> & Pick<NoteInput, 'layout'>;\n\nexport const noteMutationResolvers: NoteMutationResolvers = {\n createNote: async (_, { note }: { note: CreateNoteInput }) => {\n const replacedTitle = await replacePlaceholders(note.title);\n const replacedContent = await replacePlaceholders(note.content);\n const parsedContent = replacedContent ? parseRequiredNoteContent(replacedContent) : null;\n const blocks = parsedContent ? extractBlocksByType<{ id: string }>('tag', parsedContent) : [];\n const createdNote = await models.note.create({\n data: {\n title: replacedTitle,\n content: replacedContent,\n ...buildNoteSearchProjection({\n title: replacedTitle,\n content: replacedContent,\n }),\n ...(note.layout && { layout: note.layout }),\n },\n });\n\n if (!replacedContent) {\n return createdNote;\n }\n\n return models.note.update({\n where: { id: createdNote.id },\n data: { tags: { set: toTagConnections(blocks) } },\n });\n },\n updateNote: async (\n _,\n {\n id,\n note,\n editSessionId,\n expectedUpdatedAt,\n force,\n }: {\n id: number;\n note: NoteInput;\n editSessionId?: string;\n expectedUpdatedAt?: string;\n force?: boolean;\n },\n context: {\n req?: Request;\n },\n ) => {\n const parsedContent = note.content ? parseRequiredNoteContent(note.content) : null;\n const blocks = parsedContent ? extractBlocksByType<{ id: string }>('tag', parsedContent) : [];\n try {\n const updatedNote = await updateNoteWithVersionGuard({\n id: Number(id),\n data: {\n ...(note.title !== undefined ? { title: note.title } : {}),\n ...(note.content !== undefined ? { content: note.content } : {}),\n ...(note.layout !== undefined ? { layout: note.layout } : {}),\n ...(note.content !== undefined ? { tagIds: toTagConnections(blocks).map((tag) => tag.id) } : {}),\n },\n ...(editSessionId ? { editSessionId } : {}),\n ...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),\n ...(force ? { force: true } : {}),\n snapshotMeta: createSnapshotMetaFromUserAgent(getRequestUserAgent(context.req)),\n });\n\n if (!updatedNote) {\n throw 'NOT FOUND';\n }\n\n return updatedNote;\n } catch (error) {\n if (isNoteVersionConflictError(error)) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n currentUpdatedAt: error.currentUpdatedAt,\n expectedUpdatedAt: error.expectedUpdatedAt,\n },\n });\n }\n\n if (isInvalidNoteVersionError(error)) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n },\n });\n }\n\n if (isMissingNoteVersionError(error)) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n },\n });\n }\n\n throw error;\n }\n },\n deleteNote: async (_, { id }: { id: string }) => {\n const trashedNote = await trashNoteById(Number(id));\n\n if (!trashedNote) {\n throw 'NOT FOUND';\n }\n\n return true;\n },\n restoreNoteSnapshot: async (\n _,\n { id }: { id: string },\n context: {\n req?: Request;\n },\n ) => {\n const note = await restoreNoteSnapshot(Number(id), {\n meta: createSnapshotMetaFromUserAgent(getRequestUserAgent(context.req)),\n });\n\n if (!note) {\n throw 'NOT FOUND';\n }\n\n return note;\n },\n restoreTrashedNote: async (_, { id }: { id: string }) => {\n const note = await restoreTrashedNoteById(Number(id));\n\n if (!note) {\n throw 'NOT FOUND';\n }\n\n return note;\n },\n purgeTrashedNote: async (_, { id }: { id: string }) => {\n const purgedNote = await purgeTrashedNoteById(Number(id));\n\n if (!purgedNote) {\n throw 'NOT FOUND';\n }\n\n return true;\n },\n createNotePropertyKey: async (_, { input }: { input: NotePropertyDefinitionInput }) => {\n try {\n return await createNotePropertyDefinition(input);\n } catch (error) {\n if (error instanceof InvalidNotePropertyInputError) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: 'INVALID_NOTE_PROPERTY_INPUT',\n },\n });\n }\n\n throw error;\n }\n },\n updateNotePropertyKey: async (_, { key, input }: { key: string; input: NotePropertyDefinitionUpdateInput }) => {\n try {\n const result = await updateNotePropertyDefinition({ key, input });\n\n if (!result) {\n throw 'NOT FOUND';\n }\n\n return result;\n } catch (error) {\n if (error instanceof InvalidNotePropertyInputError) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: 'INVALID_NOTE_PROPERTY_INPUT',\n },\n });\n }\n\n throw error;\n }\n },\n deleteNotePropertyKey: async (_, { key, confirmImpact }: { key: string; confirmImpact?: boolean }) => {\n try {\n const result = await deleteNotePropertyDefinition({ key, confirmImpact });\n\n if (!result) {\n throw 'NOT FOUND';\n }\n\n return result;\n } catch (error) {\n if (error instanceof InvalidNotePropertyInputError) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: 'INVALID_NOTE_PROPERTY_INPUT',\n },\n });\n }\n\n if (error instanceof NotePropertyDeleteConfirmationRequiredError) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n affectedNoteCount: error.affectedNoteCount,\n },\n });\n }\n\n throw error;\n }\n },\n updateNoteProperties: async (\n _,\n {\n id,\n patch,\n editSessionId,\n expectedUpdatedAt,\n force,\n }: {\n id: string;\n patch: NotePropertiesPatchInput;\n editSessionId?: string;\n expectedUpdatedAt?: string;\n force?: boolean;\n },\n ) => {\n try {\n const note = await updateNotePropertiesWithVersionGuard({\n id: Number(id),\n patch,\n ...(editSessionId ? { editSessionId } : {}),\n ...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),\n ...(force ? { force: true } : {}),\n });\n\n if (!note) {\n throw 'NOT FOUND';\n }\n\n return note;\n } catch (error) {\n if (error instanceof InvalidNotePropertyInputError) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: 'INVALID_NOTE_PROPERTY_INPUT',\n },\n });\n }\n\n if (isNoteVersionConflictError(error)) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n currentUpdatedAt: error.currentUpdatedAt,\n expectedUpdatedAt: error.expectedUpdatedAt,\n },\n });\n }\n\n if (isInvalidNoteVersionError(error)) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n },\n });\n }\n\n if (isMissingNoteVersionError(error)) {\n throw new GraphQLError(error.message, {\n extensions: {\n code: error.code,\n },\n });\n }\n\n throw error;\n }\n },\n pinNote: (_, { id, pinned }: { id: string; pinned: boolean }) =>\n models.note.update({\n where: { id: Number(id) },\n data: { pinned: Boolean(pinned) },\n }),\n reorderNotes: async (_, { notes }: { notes: Array<{ id: string; order: number }> }) => {\n const updatePromises = notes.map(({ id, order }) =>\n models.note.update({\n where: { id: Number(id) },\n data: { order },\n }),\n );\n\n return Promise.all(updatePromises);\n },\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/graphql/note.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { getNoteCleanupPreview, listNoteCleanupCandidates } from '~/features/note/services/cleanup.js';\nimport {\n buildNoteGraph,\n contentReferencesNote,\n extractReferenceBlocksFromContent,\n normalizeReferenceId,\n syncReferenceTitlesInContent,\n} from '~/features/note/services/content-blocks.js';\nimport { listNotePropertyKeys } from '~/features/note/services/properties.js';\nimport {\n buildNoteSearchProjection,\n filterNotesBySearchQuery,\n NOTE_SEARCH_TEXT_SCHEMA_VERSION,\n parseNoteSearchQuery,\n} from '~/features/note/services/search.js';\nimport { diffNoteSnapshot, getNoteSnapshot, listNoteSnapshots } from '~/features/note/services/snapshot.js';\nimport {\n buildNoteTagNamesWhere,\n type NoteTagMatchMode,\n normalizeNoteTagNames,\n} from '~/features/note/services/tag-filter.js';\nimport { getTrashedNoteById, listTrashedNotes } from '~/features/note/services/trash.js';\nimport type { Note, Prisma } from '~/models.js';\nimport models from '~/models.js';\nimport { runDataMaintenanceInBackground } from '~/modules/data-maintenance.js';\nimport type { Pagination, SearchFilter } from '~/types/index.js';\n\ninterface AllNotesResolverDeps {\n countNotes: (args: { where?: Prisma.NoteWhereInput }) => Promise<number>;\n findNotes: (args: {\n orderBy: Prisma.NoteOrderByWithRelationInput[];\n where?: Prisma.NoteWhereInput;\n take?: number;\n skip?: number;\n }) => Promise<Note[]>;\n triggerSearchBackfill: () => void;\n}\n\nconst MAX_SQLITE_INT_ID = 2_147_483_647;\n\nconst isValidNoteIdString = (id: string) => {\n if (!/^[1-9]\\d*$/.test(id)) {\n return false;\n }\n\n const numericId = Number(id);\n\n return Number.isSafeInteger(numericId) && numericId <= MAX_SQLITE_INT_ID;\n};\n\ninterface NoteResolverDeps {\n findNote: (id: number) => Promise<Note | null>;\n findReferenceNotes: (ids: number[]) => Promise<Note[]>;\n updateNoteContent: (input: {\n id: number;\n updatedAt: Date;\n content: string;\n searchableText: string;\n searchableTextVersion: number;\n }) => Promise<Note>;\n isRecordNotFoundError: (error: unknown) => boolean;\n}\n\nconst buildAllNotesOrderBy = (searchFilter: SearchFilter) => {\n const sortBy = searchFilter.sortBy || 'updatedAt';\n const sortOrder = searchFilter.sortOrder || 'desc';\n const pinnedFirst = searchFilter.pinnedFirst || false;\n const orderBy: Prisma.NoteOrderByWithRelationInput[] = [];\n\n if (pinnedFirst) {\n orderBy.push({ pinned: 'desc' });\n }\n\n if (sortBy === 'createdAt') {\n orderBy.push({ createdAt: sortOrder as 'asc' | 'desc' });\n } else {\n orderBy.push({ updatedAt: sortOrder as 'asc' | 'desc' });\n }\n\n return orderBy;\n};\n\nconst buildAllNotesSearchWhere = (searchFilter: SearchFilter) => {\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n\n if (!parsedQuery.hasFilters) {\n return undefined;\n }\n\n return {\n AND: [\n { searchableTextVersion: NOTE_SEARCH_TEXT_SCHEMA_VERSION },\n ...parsedQuery.included.map((keyword) => ({ searchableText: { contains: keyword } })),\n ...parsedQuery.excluded.map((keyword) => ({ NOT: { searchableText: { contains: keyword } } })),\n ],\n } satisfies Prisma.NoteWhereInput;\n};\n\nconst buildAllNotesStaleCandidateWhere = (searchFilter: SearchFilter) => {\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n\n return {\n AND: [\n { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },\n ...parsedQuery.included.map((keyword) => ({\n OR: [{ title: { contains: keyword } }, { content: { contains: keyword } }],\n })),\n ],\n } satisfies Prisma.NoteWhereInput;\n};\n\nconst compareNotesForSearch = (left: Note, right: Note, searchFilter: SearchFilter) => {\n if (searchFilter.pinnedFirst && left.pinned !== right.pinned) {\n return left.pinned ? -1 : 1;\n }\n\n const sortBy = searchFilter.sortBy || 'updatedAt';\n const sortOrder = searchFilter.sortOrder || 'desc';\n const leftValue = sortBy === 'createdAt' ? left.createdAt.getTime() : left.updatedAt.getTime();\n const rightValue = sortBy === 'createdAt' ? right.createdAt.getTime() : right.updatedAt.getTime();\n\n if (leftValue === rightValue) {\n return 0;\n }\n\n return sortOrder === 'asc' ? leftValue - rightValue : rightValue - leftValue;\n};\n\nconst mergeSortedNotesForSearch = (left: Note[], right: Note[], searchFilter: SearchFilter) => {\n const merged: Note[] = [];\n let leftIndex = 0;\n let rightIndex = 0;\n\n while (leftIndex < left.length && rightIndex < right.length) {\n if (compareNotesForSearch(left[leftIndex], right[rightIndex], searchFilter) <= 0) {\n merged.push(left[leftIndex]);\n leftIndex += 1;\n } else {\n merged.push(right[rightIndex]);\n rightIndex += 1;\n }\n }\n\n while (leftIndex < left.length) {\n merged.push(left[leftIndex]);\n leftIndex += 1;\n }\n\n while (rightIndex < right.length) {\n merged.push(right[rightIndex]);\n rightIndex += 1;\n }\n\n return merged;\n};\n\nconst isRecordNotFoundError = (error: unknown) => {\n return (\n typeof error === 'object' && error !== null && 'code' in error && (error as { code?: unknown }).code === 'P2025'\n );\n};\n\nexport const createAllNotesQueryResolver = (\n deps: AllNotesResolverDeps = {\n countNotes: ({ where }) => models.note.count({ where }),\n findNotes: ({ orderBy, where, take, skip }) =>\n models.note.findMany({\n orderBy,\n where,\n take,\n skip,\n }),\n triggerSearchBackfill: () => {\n void runDataMaintenanceInBackground();\n },\n },\n) => {\n return async (\n _: unknown,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const orderBy = buildAllNotesOrderBy(searchFilter);\n const limit = Number(pagination.limit);\n const offset = Number(pagination.offset);\n const where = buildAllNotesSearchWhere(searchFilter);\n\n if (!where) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({}),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n const staleCandidateNotes = await deps.findNotes({\n orderBy,\n where: buildAllNotesStaleCandidateWhere(searchFilter),\n });\n\n if (staleCandidateNotes.length === 0) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({ where }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n deps.triggerSearchBackfill();\n\n const parsedQuery = parseNoteSearchQuery(searchFilter.query);\n const staleNotes = filterNotesBySearchQuery(staleCandidateNotes, parsedQuery);\n\n if (staleNotes.length === 0) {\n const [notes, totalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: limit,\n skip: offset,\n }),\n deps.countNotes({ where }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n }\n\n const [freshNotesPrefix, freshTotalCount] = await Promise.all([\n deps.findNotes({\n orderBy,\n where,\n take: offset + limit,\n }),\n deps.countNotes({ where }),\n ]);\n\n const mergedNotes = mergeSortedNotesForSearch(freshNotesPrefix, staleNotes, searchFilter);\n\n return {\n totalCount: freshTotalCount + staleNotes.length,\n notes: mergedNotes.slice(offset, offset + limit),\n };\n };\n};\n\ntype NoteQueryResolvers = NonNullable<IResolvers['Query']>;\n\ninterface BackReferencesQueryResolverDeps {\n findCandidateNotes: (noteId: number) => Promise<Note[]>;\n}\n\nexport const createBackReferencesQueryResolver = (\n deps: BackReferencesQueryResolverDeps = {\n findCandidateNotes: (noteId) =>\n models.note.findMany({\n orderBy: [{ pinned: 'desc' }, { updatedAt: 'desc' }],\n where: {\n NOT: { id: noteId },\n },\n }),\n },\n) => {\n return async (_: unknown, { id }: { id: string }) => {\n const notes = await deps.findCandidateNotes(Number(id));\n\n return notes.filter((note) => contentReferencesNote(note.content, id));\n };\n};\n\ninterface NoteGraphQueryResolverDeps {\n findNotes: () => Promise<Array<Pick<Note, 'id' | 'title' | 'content'>>>;\n}\n\nexport const createNoteGraphQueryResolver = (\n deps: NoteGraphQueryResolverDeps = {\n findNotes: () =>\n models.note.findMany({\n select: {\n id: true,\n title: true,\n content: true,\n },\n }),\n },\n) => {\n return async () => {\n const notes = await deps.findNotes();\n\n return buildNoteGraph(notes);\n };\n};\n\nexport const createNoteQueryResolver = (\n deps: NoteResolverDeps = {\n findNote: (id) => models.note.findUnique({ where: { id } }),\n findReferenceNotes: (ids) => models.note.findMany({ where: { id: { in: ids } } }),\n updateNoteContent: ({ id, updatedAt, content, searchableText, searchableTextVersion }) =>\n models.note.update({\n where: {\n id,\n updatedAt,\n },\n data: {\n content,\n searchableText,\n searchableTextVersion,\n },\n }),\n isRecordNotFoundError,\n },\n) => {\n return async (_: unknown, { id }: { id: string }) => {\n const note = await deps.findNote(Number(id));\n\n if (!note) {\n throw 'NOT FOUND';\n }\n\n if (!note.content) {\n return note;\n }\n\n const blocks = extractReferenceBlocksFromContent(note.content);\n\n if (blocks.length === 0) {\n return note;\n }\n\n const referenceIds = Array.from(\n new Set(\n blocks\n .map((block) => normalizeReferenceId(block.props?.id))\n .filter(\n (referenceId): referenceId is string =>\n referenceId !== null && isValidNoteIdString(referenceId),\n )\n .map(Number),\n ),\n );\n const references = await deps.findReferenceNotes(referenceIds);\n const titlesById = new Map(\n references.map((referenceNote: Note) => [String(referenceNote.id), referenceNote.title]),\n );\n const newContent = syncReferenceTitlesInContent(note.content, titlesById);\n\n if (!newContent || newContent === note.content) {\n return note;\n }\n\n try {\n return await deps.updateNoteContent({\n id: note.id,\n updatedAt: note.updatedAt,\n content: newContent,\n ...buildNoteSearchProjection({\n title: note.title,\n content: newContent,\n }),\n });\n } catch (error) {\n if (deps.isRecordNotFoundError(error)) {\n return note;\n }\n\n throw error;\n }\n };\n};\n\nexport const noteQueryResolvers: NoteQueryResolvers = {\n allNotes: createAllNotesQueryResolver(),\n notesInDateRange: async (\n _,\n {\n dateRange,\n }: {\n dateRange: {\n start: string;\n end: string;\n };\n },\n ) => {\n const where: Prisma.NoteWhereInput = {\n OR: [\n {\n updatedAt: {\n gte: new Date(dateRange.start),\n lte: new Date(dateRange.end),\n },\n },\n {\n createdAt: {\n gte: new Date(dateRange.start),\n lte: new Date(dateRange.end),\n },\n },\n ],\n };\n\n return models.note.findMany({\n orderBy: { createdAt: 'asc' },\n where,\n });\n },\n tagNotes: async (\n _,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const where: Prisma.NoteWhereInput = { tags: { some: { id: Number(searchFilter.query) } } };\n const notes = models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n });\n\n return {\n totalCount: models.note.count({ where }),\n notes,\n };\n },\n notesByTagNames: async (\n _,\n {\n tagNames,\n mode,\n pagination,\n }: {\n tagNames: string[];\n mode: NoteTagMatchMode;\n pagination: Pagination;\n },\n ) => {\n const normalizedTagNames = normalizeNoteTagNames(tagNames);\n\n if (normalizedTagNames.length === 0) {\n return {\n totalCount: 0,\n notes: [],\n };\n }\n\n const where = buildNoteTagNamesWhere(normalizedTagNames, mode);\n const notes = models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n });\n\n return {\n totalCount: models.note.count({ where }),\n notes,\n };\n },\n pinnedNotes: async () =>\n models.note.findMany({\n orderBy: [{ order: 'asc' }, { updatedAt: 'desc' }],\n where: { pinned: true },\n }),\n imageNotes: async (_, { src }: { src: string }) =>\n models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where: { content: { contains: src } },\n }),\n backReferences: createBackReferencesQueryResolver(),\n note: createNoteQueryResolver(),\n noteCleanupCandidates: async (\n _,\n {\n query,\n pagination = {\n limit: 20,\n offset: 0,\n },\n }: {\n query?: string;\n pagination: Pagination;\n },\n ) => {\n const result = await listNoteCleanupCandidates({\n keywords: query\n ? query\n .split(/[,\\s]+/)\n .map((keyword) => keyword.trim())\n .filter(Boolean)\n : undefined,\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n\n return result.notes;\n },\n noteCleanupPreview: async (_, { id }: { id: string }) => {\n return getNoteCleanupPreview(Number(id));\n },\n noteSnapshots: async (\n _,\n {\n id,\n limit = 10,\n }: {\n id: string;\n limit?: number;\n },\n ) => {\n return listNoteSnapshots(Number(id), Number(limit));\n },\n noteSnapshot: async (_, { id }: { id: string }) => {\n return getNoteSnapshot(Number(id));\n },\n noteSnapshotDiff: async (\n _,\n {\n id,\n compareToSnapshotId,\n target,\n contextLines,\n }: {\n id: string;\n compareToSnapshotId?: string;\n target?: 'NEXT' | 'PREVIOUS' | 'CURRENT';\n contextLines?: number | null;\n },\n ) => {\n const normalizedContextLines = contextLines ?? 3;\n\n if (normalizedContextLines < 0 || normalizedContextLines > 20) {\n throw new Error('INVALID_SNAPSHOT_DIFF_CONTEXT_LINES');\n }\n\n return diffNoteSnapshot(Number(id), {\n ...(compareToSnapshotId !== undefined ? { compareToSnapshotId: Number(compareToSnapshotId) } : {}),\n ...(target !== undefined ? { target: target.toLowerCase() as 'next' | 'previous' | 'current' } : {}),\n contextLines: normalizedContextLines,\n });\n },\n notePropertyKeys: async (\n _,\n {\n query,\n pagination = {\n limit: 50,\n offset: 0,\n },\n }: {\n query?: string;\n pagination: Pagination;\n },\n ) => {\n return listNotePropertyKeys({\n query,\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n },\n trashedNote: async (_, { id }: { id: string }) => {\n return getTrashedNoteById(Number(id));\n },\n trashedNotes: async (\n _,\n {\n pagination = {\n limit: 25,\n offset: 0,\n },\n }: {\n pagination: Pagination;\n },\n ) => {\n return listTrashedNotes({\n limit: Number(pagination.limit),\n offset: Number(pagination.offset),\n });\n },\n noteGraph: createNoteGraphQueryResolver(),\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/graphql/note.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { noteFieldResolvers, noteSnapshotFieldResolvers } 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 NoteSnapshot: noteSnapshotFieldResolvers,\n};\n"],"mappings":"AACA,SAAS,oBAAoB,kCAAkC;AAC/D,SAAS,6BAA6B;AACtC,SAAsC,0BAA0B;AAEhE,SAAS,+BAAAA,oCAAmC;AAErC,MAAM,gBAA4B;AAAA,EACrC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AAAA,EACN,cAAc;AAClB;","names":["createAllNotesQueryResolver"]}
@@ -1 +0,0 @@
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 enum NotePropertyValueType {\n text\n url\n number\n date\n boolean\n select\n }\n\n input NoteInput {\n title: String\n content: String\n layout: NoteLayout\n }\n\n input NotePropertySetInput {\n key: String!\n name: String\n value: String!\n valueType: NotePropertyValueType!\n }\n\n input NotePropertyOptionInput {\n label: String!\n value: String\n color: String\n order: Int\n }\n\n input NotePropertyOptionUpdateInput {\n id: ID\n label: String!\n value: String\n color: String\n order: Int\n }\n\n input NotePropertyDefinitionInput {\n key: String!\n name: String\n valueType: NotePropertyValueType!\n options: [NotePropertyOptionInput!]\n }\n\n input NotePropertyDefinitionUpdateInput {\n name: String\n options: [NotePropertyOptionUpdateInput!]\n }\n\n input NotePropertiesPatchInput {\n set: [NotePropertySetInput!]\n deleteKeys: [String!]\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 properties: [NoteProperty!]!\n }\n\n type NoteProperty {\n key: String!\n name: String!\n value: String!\n valueType: NotePropertyValueType!\n createdAt: String!\n option: NotePropertyOption\n updatedAt: String!\n }\n\n type NotePropertyOption {\n id: ID!\n label: String!\n value: String!\n color: String\n order: Int!\n }\n\n type NotePropertyKey {\n key: String!\n name: String!\n valueType: NotePropertyValueType!\n noteCount: Int!\n options: [NotePropertyOption!]!\n updatedAt: String!\n }\n\n type NotePropertyKeys {\n totalCount: Int!\n keys: [NotePropertyKey!]!\n }\n\n type NotePropertyDeleteResult {\n key: String!\n name: String!\n valueType: NotePropertyValueType!\n affectedNoteCount: Int!\n deleted: Boolean!\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 contentPreview: String!\n contentAsMarkdown: String!\n createdAt: String!\n meta: NoteSnapshotMeta!\n }\n\n enum NoteSnapshotDiffTarget {\n NEXT\n PREVIOUS\n CURRENT\n }\n\n type NoteSnapshotDiffEndpoint {\n kind: String!\n id: ID!\n title: String!\n createdAt: String\n updatedAt: String\n meta: NoteSnapshotMeta\n }\n\n type NoteSnapshotDiffDetail {\n markdown: String!\n changedLineCount: Int!\n changedCharCount: Int!\n beforeMarkdownSha256: String!\n afterMarkdownSha256: String!\n }\n\n type NoteSnapshotDiff {\n noteId: ID!\n mode: String!\n before: NoteSnapshotDiffEndpoint!\n after: NoteSnapshotDiffEndpoint!\n diff: NoteSnapshotDiffDetail!\n }\n\n type DeletedNote {\n id: ID!\n title: String!\n createdAt: String!\n updatedAt: String!\n deletedAt: String!\n contentPreview: String!\n contentAsMarkdown: 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 noteSnapshot(id: ID!): NoteSnapshot\n noteSnapshotDiff(id: ID!, compareToSnapshotId: ID, target: NoteSnapshotDiffTarget, contextLines: Int): NoteSnapshotDiff\n notePropertyKeys(query: String, pagination: PaginationInput): NotePropertyKeys!\n trashedNote(id: ID!): DeletedNote\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, expectedUpdatedAt: String, force: Boolean): Note!\n deleteNote(id: ID!): Boolean!\n restoreNoteSnapshot(id: ID!): Note!\n restoreTrashedNote(id: ID!): Note!\n purgeTrashedNote(id: ID!): Boolean!\n createNotePropertyKey(input: NotePropertyDefinitionInput!): NotePropertyKey!\n updateNotePropertyKey(key: String!, input: NotePropertyDefinitionUpdateInput!): NotePropertyKey!\n deleteNotePropertyKey(key: String!, confirmImpact: Boolean): NotePropertyDeleteResult!\n updateNoteProperties(\n id: ID!\n patch: NotePropertiesPatchInput!\n editSessionId: String\n expectedUpdatedAt: String!\n force: Boolean\n ): Note!\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;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;AA2PjB,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;AAAA;AAAA;AAAA;AAAA;AAsClB,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBrB,MAAM,eAAe;AAAA,MACtB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA;","names":[]}
@@ -1 +0,0 @@
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 {\n appendNoteMarkdown,\n patchNoteMarkdown,\n replaceNoteMarkdown,\n updateNoteMetadata,\n} from '~/features/note/services/markdown-intent-write.js';\nimport type {\n MarkdownAppendPlacement,\n MarkdownChangePolicy,\n MarkdownPatchOperation,\n MarkdownPatchSelector,\n} from '~/features/note/services/markdown-patch.js';\nimport type { NotePropertiesByKeyPatchInput } from '~/features/note/services/properties.js';\nimport { MCP_SNAPSHOT_META } from '~/features/note/services/snapshot.js';\nimport models, { 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 POSITION_HINTS = new Set(['first', 'last']);\nconst PRESERVATION_POLICIES = new Set<unknown>([true, false, 'warn']);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === 'object' && value !== null;\n};\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\nconst resolveOptionalString = (value: unknown, code: string, message: string) => {\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value === 'string') {\n return value;\n }\n\n throw createAppError(400, code, message);\n};\n\nconst resolveMetadataPropertyPatch = (value: unknown): NotePropertiesByKeyPatchInput | undefined => {\n if (value === undefined) {\n return undefined;\n }\n\n if (!isRecord(value)) {\n throw createAppError(400, 'INVALID_NOTE_PROPERTIES', 'Properties patch must be an object.');\n }\n\n const set =\n value.set === undefined\n ? undefined\n : Array.isArray(value.set)\n ? value.set.map((item) => {\n if (!isRecord(item) || typeof item.key !== 'string' || typeof item.value !== 'string') {\n throw createAppError(\n 400,\n 'INVALID_NOTE_PROPERTIES',\n 'Property set items must include string key and value fields.',\n );\n }\n\n return {\n key: item.key,\n value: item.value,\n };\n })\n : null;\n\n if (set === null) {\n throw createAppError(400, 'INVALID_NOTE_PROPERTIES', 'Property set must be an array.');\n }\n\n const deleteKeys =\n value.deleteKeys === undefined\n ? undefined\n : Array.isArray(value.deleteKeys)\n ? value.deleteKeys.map((key) => {\n if (typeof key !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_PROPERTIES', 'Property deleteKeys must be strings.');\n }\n\n return key;\n })\n : null;\n\n if (deleteKeys === null) {\n throw createAppError(400, 'INVALID_NOTE_PROPERTIES', 'Property deleteKeys must be an array.');\n }\n\n return {\n ...(set !== undefined ? { set } : {}),\n ...(deleteKeys !== undefined ? { deleteKeys } : {}),\n };\n};\n\nconst resolvePositiveNoteId = (value: unknown) => {\n const noteId = Number(value);\n\n if (!Number.isInteger(noteId) || noteId <= 0) {\n throw createAppError(400, 'INVALID_NOTE_ID', 'A valid note id is required.');\n }\n\n return noteId;\n};\n\nconst serializeNoteWriteBaseline = (note: { id: number | string; updatedAt: Date | string }) => {\n return {\n id: String(note.id),\n updatedAt: note.updatedAt instanceof Date ? note.updatedAt.toISOString() : note.updatedAt,\n };\n};\n\nconst isNonNegativeInteger = (value: unknown): value is number => {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0;\n};\n\nconst isPositiveInteger = (value: unknown): value is number => {\n return typeof value === 'number' && Number.isInteger(value) && value > 0;\n};\n\nconst resolveMarkdownWritePolicy = (value: unknown): MarkdownChangePolicy | undefined => {\n if (value === undefined) {\n return undefined;\n }\n\n if (!isRecord(value)) {\n throw createAppError(400, 'INVALID_MARKDOWN_POLICY', 'Markdown write policy must be an object.');\n }\n\n const policy: MarkdownChangePolicy = {};\n\n if (value.allowNoop !== undefined) {\n if (typeof value.allowNoop !== 'boolean') {\n throw createAppError(400, 'INVALID_MARKDOWN_POLICY', 'allowNoop must be a boolean.');\n }\n policy.allowNoop = value.allowNoop;\n }\n\n if (value.preserveTags !== undefined) {\n if (!PRESERVATION_POLICIES.has(value.preserveTags)) {\n throw createAppError(400, 'INVALID_MARKDOWN_POLICY', 'preserveTags must be boolean or warn.');\n }\n policy.preserveTags = value.preserveTags as MarkdownChangePolicy['preserveTags'];\n }\n\n if (value.preserveReferences !== undefined) {\n if (!PRESERVATION_POLICIES.has(value.preserveReferences)) {\n throw createAppError(400, 'INVALID_MARKDOWN_POLICY', 'preserveReferences must be boolean or warn.');\n }\n policy.preserveReferences = value.preserveReferences as MarkdownChangePolicy['preserveReferences'];\n }\n\n return policy;\n};\n\nconst resolvePatchSelector = (value: unknown): MarkdownPatchSelector => {\n if (!isRecord(value)) {\n throw createAppError(400, 'INVALID_PATCH_SELECTOR', 'Patch selector must be an object.');\n }\n\n if (value.type === 'exact_text') {\n if (typeof value.text !== 'string') {\n throw createAppError(400, 'INVALID_PATCH_SELECTOR', 'Exact text selector requires text.');\n }\n\n return {\n type: 'exact_text',\n text: value.text,\n ...(value.before !== undefined\n ? {\n before: resolveOptionalString(\n value.before,\n 'INVALID_PATCH_SELECTOR',\n 'Selector before must be a string.',\n ),\n }\n : {}),\n ...(value.after !== undefined\n ? {\n after: resolveOptionalString(\n value.after,\n 'INVALID_PATCH_SELECTOR',\n 'Selector after must be a string.',\n ),\n }\n : {}),\n };\n }\n\n if (value.type === 'match_candidate') {\n const { text, matchIndex, lineStart, matchSha256, surroundingHash, positionHint } = value;\n\n if (\n typeof text !== 'string' ||\n !isNonNegativeInteger(matchIndex) ||\n !isPositiveInteger(lineStart) ||\n typeof matchSha256 !== 'string' ||\n typeof surroundingHash !== 'string'\n ) {\n throw createAppError(400, 'INVALID_PATCH_SELECTOR', 'Match candidate selector has invalid fields.');\n }\n\n if (positionHint !== undefined && !POSITION_HINTS.has(String(positionHint))) {\n throw createAppError(400, 'INVALID_PATCH_SELECTOR', 'positionHint must be first or last.');\n }\n\n return {\n type: 'match_candidate',\n text,\n matchIndex,\n lineStart,\n matchSha256,\n surroundingHash,\n ...(positionHint ? { positionHint: positionHint as 'first' | 'last' } : {}),\n };\n }\n\n throw createAppError(400, 'INVALID_PATCH_SELECTOR', 'Unsupported patch selector type.');\n};\n\nconst resolvePatchOperation = (value: unknown): MarkdownPatchOperation => {\n if (!isRecord(value)) {\n throw createAppError(400, 'INVALID_PATCH_OPERATION', 'Patch operation must be an object.');\n }\n\n if (value.type === 'replace') {\n if (typeof value.replacement !== 'string') {\n throw createAppError(400, 'INVALID_PATCH_OPERATION', 'Replace operation requires replacement.');\n }\n\n return {\n type: 'replace',\n replacement: value.replacement,\n };\n }\n\n if (value.type === 'insert_before' || value.type === 'insert_after') {\n if (typeof value.insertion !== 'string') {\n throw createAppError(400, 'INVALID_PATCH_OPERATION', 'Insert operation requires insertion.');\n }\n\n return {\n type: value.type,\n insertion: value.insertion,\n };\n }\n\n throw createAppError(400, 'INVALID_PATCH_OPERATION', 'Unsupported patch operation type.');\n};\n\nconst resolveAppendPlacement = (value: unknown): MarkdownAppendPlacement | undefined => {\n if (value === undefined) {\n return undefined;\n }\n\n if (!isRecord(value)) {\n throw createAppError(400, 'INVALID_APPEND_PLACEMENT', 'Append placement must be an object.');\n }\n\n if (value.type === 'end') {\n return { type: 'end' };\n }\n\n if (value.type === 'after_heading') {\n if (typeof value.heading !== 'string') {\n throw createAppError(400, 'INVALID_APPEND_PLACEMENT', 'after_heading placement requires heading.');\n }\n\n const { heading, level } = value;\n\n if (typeof heading !== 'string') {\n throw createAppError(400, 'INVALID_APPEND_PLACEMENT', 'after_heading placement requires heading.');\n }\n\n if (level !== undefined && (typeof level !== 'number' || !Number.isInteger(level) || level < 1 || level > 6)) {\n throw createAppError(400, 'INVALID_APPEND_PLACEMENT', 'Heading level must be an integer from 1 to 6.');\n }\n\n return {\n type: 'after_heading',\n heading,\n ...(level !== undefined ? { level } : {}),\n };\n }\n\n throw createAppError(400, 'INVALID_APPEND_PLACEMENT', 'Unsupported append placement type.');\n};\n\nconst resolveSeparator = (value: unknown) => {\n if (value === undefined) {\n return undefined;\n }\n\n if (value === '\\n' || value === '\\n\\n') {\n return value;\n }\n\n throw createAppError(400, 'INVALID_SEPARATOR', 'Separator must be \"\\\\n\" or \"\\\\n\\\\n\".');\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 createMcpNoteWriteBaselineHandler = (\n findNoteBaseline: (id: number) => Promise<{ id: number; updatedAt: Date } | null> = async (id) =>\n models.note.findUnique({\n where: { id },\n select: {\n id: true,\n updatedAt: true,\n },\n }),\n): Controller => {\n return async (req, res) => {\n const noteId = resolvePositiveNoteId(req.body?.id);\n const note = await findNoteBaseline(noteId);\n\n if (!note) {\n throw createAppError(404, 'NOTE_NOT_FOUND', 'The requested note was not found.');\n }\n\n res.status(200)\n .json({\n note: serializeNoteWriteBaseline(note),\n })\n .end();\n };\n};\n\nexport const createMcpPatchNoteMarkdownHandler = (\n patchMarkdown = patchNoteMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { id, expectedUpdatedAt, baseMarkdownSha256, intent, selector, operation, policy } = req.body ?? {};\n const noteId = resolvePositiveNoteId(id);\n const resolvedExpectedUpdatedAt = resolveOptionalString(\n expectedUpdatedAt,\n 'INVALID_NOTE_VERSION',\n 'expectedUpdatedAt must be a string.',\n );\n const resolvedBaseMarkdownSha256 = resolveOptionalString(\n baseMarkdownSha256,\n 'INVALID_MARKDOWN_HASH',\n 'baseMarkdownSha256 must be a string.',\n );\n\n if (typeof intent !== 'string') {\n throw createAppError(400, 'INVALID_PATCH_INTENT', 'Patch intent must be a string.');\n }\n\n const result = await patchMarkdown({\n id: noteId,\n expectedUpdatedAt: resolvedExpectedUpdatedAt,\n baseMarkdownSha256: resolvedBaseMarkdownSha256,\n intent,\n selector: resolvePatchSelector(selector),\n operation: resolvePatchOperation(operation),\n policy: resolveMarkdownWritePolicy(policy),\n });\n\n if (result.status === 'applied') {\n emitEvent({\n type: 'mcp.note.updated',\n source: 'mcp',\n noteId: result.note.id,\n updatedAt: result.note.updatedAt,\n });\n }\n\n res.status(200).json(result).end();\n };\n};\n\nexport const createMcpAppendNoteMarkdownHandler = (\n appendMarkdown = appendNoteMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { id, expectedUpdatedAt, baseMarkdownSha256, intent, insertion, placement, separator, policy } =\n req.body ?? {};\n const noteId = resolvePositiveNoteId(id);\n const resolvedExpectedUpdatedAt = resolveOptionalString(\n expectedUpdatedAt,\n 'INVALID_NOTE_VERSION',\n 'expectedUpdatedAt must be a string.',\n );\n const resolvedBaseMarkdownSha256 = resolveOptionalString(\n baseMarkdownSha256,\n 'INVALID_MARKDOWN_HASH',\n 'baseMarkdownSha256 must be a string.',\n );\n\n if (typeof intent !== 'string') {\n throw createAppError(400, 'INVALID_APPEND_INTENT', 'Append intent must be a string.');\n }\n\n if (typeof insertion !== 'string') {\n throw createAppError(400, 'INVALID_APPEND_INSERTION', 'Append insertion must be a string.');\n }\n\n const result = await appendMarkdown({\n id: noteId,\n expectedUpdatedAt: resolvedExpectedUpdatedAt,\n baseMarkdownSha256: resolvedBaseMarkdownSha256,\n intent,\n insertion,\n placement: resolveAppendPlacement(placement),\n separator: resolveSeparator(separator),\n policy: resolveMarkdownWritePolicy(policy),\n });\n\n if (result.status === 'applied') {\n emitEvent({\n type: 'mcp.note.updated',\n source: 'mcp',\n noteId: result.note.id,\n updatedAt: result.note.updatedAt,\n });\n }\n\n res.status(200).json(result).end();\n };\n};\n\nexport const createMcpReplaceNoteMarkdownHandler = (\n replaceMarkdown = replaceNoteMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { id, expectedUpdatedAt, baseMarkdownSha256, intent, replacement, policy } = req.body ?? {};\n const noteId = resolvePositiveNoteId(id);\n const resolvedExpectedUpdatedAt = resolveOptionalString(\n expectedUpdatedAt,\n 'INVALID_NOTE_VERSION',\n 'expectedUpdatedAt must be a string.',\n );\n const resolvedBaseMarkdownSha256 = resolveOptionalString(\n baseMarkdownSha256,\n 'INVALID_MARKDOWN_HASH',\n 'baseMarkdownSha256 must be a string.',\n );\n\n if (typeof intent !== 'string') {\n throw createAppError(400, 'INVALID_REPLACE_INTENT', 'Replace intent must be a string.');\n }\n\n if (typeof replacement !== 'string') {\n throw createAppError(400, 'INVALID_REPLACE_MARKDOWN', 'Replacement markdown must be a string.');\n }\n\n const result = await replaceMarkdown({\n id: noteId,\n expectedUpdatedAt: resolvedExpectedUpdatedAt,\n baseMarkdownSha256: resolvedBaseMarkdownSha256,\n intent,\n replacement,\n policy: resolveMarkdownWritePolicy(policy),\n });\n\n if (result.status === 'applied') {\n emitEvent({\n type: 'mcp.note.updated',\n source: 'mcp',\n noteId: result.note.id,\n updatedAt: result.note.updatedAt,\n });\n }\n\n res.status(200).json(result).end();\n };\n};\n\nexport const createMcpUpdateNoteMetadataHandler = (\n updateMetadata = updateNoteMetadata,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { id, expectedUpdatedAt, title, layout, properties } = req.body ?? {};\n const noteId = resolvePositiveNoteId(id);\n const resolvedLayout = resolveNoteLayout(layout);\n const resolvedProperties = resolveMetadataPropertyPatch(properties);\n\n if (typeof expectedUpdatedAt !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_VERSION', 'expectedUpdatedAt must be a string.');\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 (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 (title === undefined && layout === undefined && resolvedProperties === undefined) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', 'At least one metadata field must be provided.');\n }\n\n const result = await updateMetadata({\n id: noteId,\n expectedUpdatedAt,\n ...(title !== undefined ? { title } : {}),\n ...(resolvedLayout ? { layout: resolvedLayout } : {}),\n ...(resolvedProperties !== undefined ? { properties: resolvedProperties } : {}),\n });\n\n if (result.status === 'applied') {\n emitEvent({\n type: 'mcp.note.updated',\n source: 'mcp',\n noteId: result.note.id,\n updatedAt: result.note.updatedAt,\n });\n }\n\n res.status(200).json(result).end();\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":"AAAA;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,sBAAsB;AAC/B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAQP,SAAS,yBAAyB;AAClC,OAAO,YAAiC;AACxC,SAAS,sBAAsB;AAC/B,SAAS,uBAA8C;AAGvD,MAAM,eAAe,oBAAI,IAAgB,CAAC,UAAU,QAAQ,MAAM,CAAC;AAEnE,MAAM,iBAAiB,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAChD,MAAM,wBAAwB,oBAAI,IAAa,CAAC,MAAM,OAAO,MAAM,CAAC;AAEpE,MAAM,WAAW,CAAC,UAAqD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU;AAClD;AAEA,MAAM,oBAAoB,CAAC,UAAkD;AACzE,MAAI,UAAU,QAAW;AACrB,WAAO;AAAA,EACX;AAEA,MAAI,OAAO,UAAU,YAAY,aAAa,IAAI,KAAmB,GAAG;AACpE,WAAO;AAAA,EACX;AAEA,SAAO;AACX;AAEA,MAAM,wBAAwB,CAAC,OAAgB,MAAc,YAAoB;AAC7E,MAAI,UAAU,QAAW;AACrB,WAAO;AAAA,EACX;AAEA,MAAI,OAAO,UAAU,UAAU;AAC3B,WAAO;AAAA,EACX;AAEA,QAAM,eAAe,KAAK,MAAM,OAAO;AAC3C;AAEA,MAAM,+BAA+B,CAAC,UAA8D;AAChG,MAAI,UAAU,QAAW;AACrB,WAAO;AAAA,EACX;AAEA,MAAI,CAAC,SAAS,KAAK,GAAG;AAClB,UAAM,eAAe,KAAK,2BAA2B,qCAAqC;AAAA,EAC9F;AAEA,QAAM,MACF,MAAM,QAAQ,SACR,SACA,MAAM,QAAQ,MAAM,GAAG,IACrB,MAAM,IAAI,IAAI,CAAC,SAAS;AACpB,QAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,UAAU,UAAU;AACnF,YAAM;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,MACJ;AAAA,IACJ;AAEA,WAAO;AAAA,MACH,KAAK,KAAK;AAAA,MACV,OAAO,KAAK;AAAA,IAChB;AAAA,EACJ,CAAC,IACD;AAEZ,MAAI,QAAQ,MAAM;AACd,UAAM,eAAe,KAAK,2BAA2B,gCAAgC;AAAA,EACzF;AAEA,QAAM,aACF,MAAM,eAAe,SACf,SACA,MAAM,QAAQ,MAAM,UAAU,IAC5B,MAAM,WAAW,IAAI,CAAC,QAAQ;AAC1B,QAAI,OAAO,QAAQ,UAAU;AACzB,YAAM,eAAe,KAAK,2BAA2B,sCAAsC;AAAA,IAC/F;AAEA,WAAO;AAAA,EACX,CAAC,IACD;AAEZ,MAAI,eAAe,MAAM;AACrB,UAAM,eAAe,KAAK,2BAA2B,uCAAuC;AAAA,EAChG;AAEA,SAAO;AAAA,IACH,GAAI,QAAQ,SAAY,EAAE,IAAI,IAAI,CAAC;AAAA,IACnC,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,EACrD;AACJ;AAEA,MAAM,wBAAwB,CAAC,UAAmB;AAC9C,QAAM,SAAS,OAAO,KAAK;AAE3B,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,UAAU,GAAG;AAC1C,UAAM,eAAe,KAAK,mBAAmB,8BAA8B;AAAA,EAC/E;AAEA,SAAO;AACX;AAEA,MAAM,6BAA6B,CAAC,SAA4D;AAC5F,SAAO;AAAA,IACH,IAAI,OAAO,KAAK,EAAE;AAAA,IAClB,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI,KAAK;AAAA,EACpF;AACJ;AAEA,MAAM,uBAAuB,CAAC,UAAoC;AAC9D,SAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS;AAC5E;AAEA,MAAM,oBAAoB,CAAC,UAAoC;AAC3D,SAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ;AAC3E;AAEA,MAAM,6BAA6B,CAAC,UAAqD;AACrF,MAAI,UAAU,QAAW;AACrB,WAAO;AAAA,EACX;AAEA,MAAI,CAAC,SAAS,KAAK,GAAG;AAClB,UAAM,eAAe,KAAK,2BAA2B,0CAA0C;AAAA,EACnG;AAEA,QAAM,SAA+B,CAAC;AAEtC,MAAI,MAAM,cAAc,QAAW;AAC/B,QAAI,OAAO,MAAM,cAAc,WAAW;AACtC,YAAM,eAAe,KAAK,2BAA2B,8BAA8B;AAAA,IACvF;AACA,WAAO,YAAY,MAAM;AAAA,EAC7B;AAEA,MAAI,MAAM,iBAAiB,QAAW;AAClC,QAAI,CAAC,sBAAsB,IAAI,MAAM,YAAY,GAAG;AAChD,YAAM,eAAe,KAAK,2BAA2B,uCAAuC;AAAA,IAChG;AACA,WAAO,eAAe,MAAM;AAAA,EAChC;AAEA,MAAI,MAAM,uBAAuB,QAAW;AACxC,QAAI,CAAC,sBAAsB,IAAI,MAAM,kBAAkB,GAAG;AACtD,YAAM,eAAe,KAAK,2BAA2B,6CAA6C;AAAA,IACtG;AACA,WAAO,qBAAqB,MAAM;AAAA,EACtC;AAEA,SAAO;AACX;AAEA,MAAM,uBAAuB,CAAC,UAA0C;AACpE,MAAI,CAAC,SAAS,KAAK,GAAG;AAClB,UAAM,eAAe,KAAK,0BAA0B,mCAAmC;AAAA,EAC3F;AAEA,MAAI,MAAM,SAAS,cAAc;AAC7B,QAAI,OAAO,MAAM,SAAS,UAAU;AAChC,YAAM,eAAe,KAAK,0BAA0B,oCAAoC;AAAA,IAC5F;AAEA,WAAO;AAAA,MACH,MAAM;AAAA,MACN,MAAM,MAAM;AAAA,MACZ,GAAI,MAAM,WAAW,SACf;AAAA,QACI,QAAQ;AAAA,UACJ,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACJ;AAAA,MACJ,IACA,CAAC;AAAA,MACP,GAAI,MAAM,UAAU,SACd;AAAA,QACI,OAAO;AAAA,UACH,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACJ;AAAA,MACJ,IACA,CAAC;AAAA,IACX;AAAA,EACJ;AAEA,MAAI,MAAM,SAAS,mBAAmB;AAClC,UAAM,EAAE,MAAM,YAAY,WAAW,aAAa,iBAAiB,aAAa,IAAI;AAEpF,QACI,OAAO,SAAS,YAChB,CAAC,qBAAqB,UAAU,KAChC,CAAC,kBAAkB,SAAS,KAC5B,OAAO,gBAAgB,YACvB,OAAO,oBAAoB,UAC7B;AACE,YAAM,eAAe,KAAK,0BAA0B,8CAA8C;AAAA,IACtG;AAEA,QAAI,iBAAiB,UAAa,CAAC,eAAe,IAAI,OAAO,YAAY,CAAC,GAAG;AACzE,YAAM,eAAe,KAAK,0BAA0B,qCAAqC;AAAA,IAC7F;AAEA,WAAO;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,eAAe,EAAE,aAA+C,IAAI,CAAC;AAAA,IAC7E;AAAA,EACJ;AAEA,QAAM,eAAe,KAAK,0BAA0B,kCAAkC;AAC1F;AAEA,MAAM,wBAAwB,CAAC,UAA2C;AACtE,MAAI,CAAC,SAAS,KAAK,GAAG;AAClB,UAAM,eAAe,KAAK,2BAA2B,oCAAoC;AAAA,EAC7F;AAEA,MAAI,MAAM,SAAS,WAAW;AAC1B,QAAI,OAAO,MAAM,gBAAgB,UAAU;AACvC,YAAM,eAAe,KAAK,2BAA2B,yCAAyC;AAAA,IAClG;AAEA,WAAO;AAAA,MACH,MAAM;AAAA,MACN,aAAa,MAAM;AAAA,IACvB;AAAA,EACJ;AAEA,MAAI,MAAM,SAAS,mBAAmB,MAAM,SAAS,gBAAgB;AACjE,QAAI,OAAO,MAAM,cAAc,UAAU;AACrC,YAAM,eAAe,KAAK,2BAA2B,sCAAsC;AAAA,IAC/F;AAEA,WAAO;AAAA,MACH,MAAM,MAAM;AAAA,MACZ,WAAW,MAAM;AAAA,IACrB;AAAA,EACJ;AAEA,QAAM,eAAe,KAAK,2BAA2B,mCAAmC;AAC5F;AAEA,MAAM,yBAAyB,CAAC,UAAwD;AACpF,MAAI,UAAU,QAAW;AACrB,WAAO;AAAA,EACX;AAEA,MAAI,CAAC,SAAS,KAAK,GAAG;AAClB,UAAM,eAAe,KAAK,4BAA4B,qCAAqC;AAAA,EAC/F;AAEA,MAAI,MAAM,SAAS,OAAO;AACtB,WAAO,EAAE,MAAM,MAAM;AAAA,EACzB;AAEA,MAAI,MAAM,SAAS,iBAAiB;AAChC,QAAI,OAAO,MAAM,YAAY,UAAU;AACnC,YAAM,eAAe,KAAK,4BAA4B,2CAA2C;AAAA,IACrG;AAEA,UAAM,EAAE,SAAS,MAAM,IAAI;AAE3B,QAAI,OAAO,YAAY,UAAU;AAC7B,YAAM,eAAe,KAAK,4BAA4B,2CAA2C;AAAA,IACrG;AAEA,QAAI,UAAU,WAAc,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC1G,YAAM,eAAe,KAAK,4BAA4B,+CAA+C;AAAA,IACzG;AAEA,WAAO;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3C;AAAA,EACJ;AAEA,QAAM,eAAe,KAAK,4BAA4B,oCAAoC;AAC9F;AAEA,MAAM,mBAAmB,CAAC,UAAmB;AACzC,MAAI,UAAU,QAAW;AACrB,WAAO;AAAA,EACX;AAEA,MAAI,UAAU,QAAQ,UAAU,QAAQ;AACpC,WAAO;AAAA,EACX;AAEA,QAAM,eAAe,KAAK,qBAAqB,sCAAsC;AACzF;AAIO,MAAM,6BAA6B,CACtC,aAAa,wBACb,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,OAAO,UAAU,OAAO,IAAI,IAAI,QAAQ,CAAC;AACjD,UAAM,iBAAiB,kBAAkB,MAAM;AAE/C,QAAI,OAAO,UAAU,UAAU;AAC3B,YAAM,eAAe,KAAK,sBAAsB,2BAA2B;AAAA,IAC/E;AAEA,QAAI,aAAa,UAAa,OAAO,aAAa,UAAU;AACxD,YAAM,eAAe,KAAK,yBAAyB,iCAAiC;AAAA,IACxF;AAEA,QAAI,WAAW,UAAa,mBAAmB,MAAM;AACjD,YAAM,eAAe,KAAK,uBAAuB,mDAAmD;AAAA,IACxG;AAEA,QAAI;AACA,YAAM,OAAO,MAAM,WAAW;AAAA,QAC1B;AAAA,QACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,QAC7C,GAAI,iBAAiB,EAAE,QAAQ,eAAe,IAAI,CAAC;AAAA,MACvD,CAAC;AAED,gBAAU;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,WAAW,KAAK;AAAA,MACpB,CAAC;AAED,UAAI,OAAO,GAAG,EACT,KAAK;AAAA,QACF,SAAS;AAAA,QACT;AAAA,MACJ,CAAC,EACA,IAAI;AAAA,IACb,SAAS,OAAO;AACZ,UAAI,iBAAiB,gCAAgC;AACjD,cAAM,eAAe,KAAK,sBAAsB,MAAM,OAAO;AAAA,MACjE;AAEA,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;AAEO,MAAM,6BAA6B,CACtC,aAAa,wBACb,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,IAAI,OAAO,UAAU,QAAQ,cAAc,IAAI,IAAI,QAAQ,CAAC;AACpE,UAAM,SAAS,OAAO,EAAE;AACxB,UAAM,iBAAiB,kBAAkB,MAAM;AAE/C,QAAI,CAAC,OAAO,UAAU,MAAM,KAAK,UAAU,GAAG;AAC1C,YAAM,eAAe,KAAK,mBAAmB,8BAA8B;AAAA,IAC/E;AAEA,QAAI,UAAU,UAAa,OAAO,UAAU,UAAU;AAClD,YAAM,eAAe,KAAK,sBAAsB,8BAA8B;AAAA,IAClF;AAEA,QAAI,aAAa,UAAa,OAAO,aAAa,UAAU;AACxD,YAAM,eAAe,KAAK,yBAAyB,iCAAiC;AAAA,IACxF;AAEA,QAAI,WAAW,UAAa,mBAAmB,MAAM;AACjD,YAAM,eAAe,KAAK,uBAAuB,mDAAmD;AAAA,IACxG;AAEA,QAAI,kBAAkB,UAAa,OAAO,kBAAkB,UAAU;AAClE,YAAM,eAAe,KAAK,2BAA2B,mCAAmC;AAAA,IAC5F;AAEA,QAAI,UAAU,UAAa,aAAa,UAAa,WAAW,QAAW;AACvE,YAAM,eAAe,KAAK,sBAAsB,sDAAsD;AAAA,IAC1G;AAEA,QAAI;AACA,YAAM,OAAO,MAAM,WAAW;AAAA,QAC1B,IAAI;AAAA,QACJ,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,QACvC,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,QAC7C,GAAI,iBAAiB,EAAE,QAAQ,eAAe,IAAI,CAAC;AAAA,QACnD,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,QACvD,cAAc;AAAA,MAClB,CAAC;AAED,UAAI,CAAC,MAAM;AACP,cAAM,eAAe,KAAK,kBAAkB,mCAAmC;AAAA,MACnF;AAEA,gBAAU;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,WAAW,KAAK;AAAA,MACpB,CAAC;AAED,UAAI,OAAO,GAAG,EACT,KAAK;AAAA,QACF,SAAS;AAAA,QACT;AAAA,MACJ,CAAC,EACA,IAAI;AAAA,IACb,SAAS,OAAO;AACZ,UAAI,iBAAiB,gCAAgC;AACjD,cAAM,eAAe,KAAK,sBAAsB,MAAM,OAAO;AAAA,MACjE;AAEA,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;AAEO,MAAM,oCAAoC,CAC7C,mBAAoF,OAAO,OACvF,OAAO,KAAK,WAAW;AAAA,EACnB,OAAO,EAAE,GAAG;AAAA,EACZ,QAAQ;AAAA,IACJ,IAAI;AAAA,IACJ,WAAW;AAAA,EACf;AACJ,CAAC,MACQ;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,SAAS,sBAAsB,IAAI,MAAM,EAAE;AACjD,UAAM,OAAO,MAAM,iBAAiB,MAAM;AAE1C,QAAI,CAAC,MAAM;AACP,YAAM,eAAe,KAAK,kBAAkB,mCAAmC;AAAA,IACnF;AAEA,QAAI,OAAO,GAAG,EACT,KAAK;AAAA,MACF,MAAM,2BAA2B,IAAI;AAAA,IACzC,CAAC,EACA,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,oCAAoC,CAC7C,gBAAgB,mBAChB,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,IAAI,mBAAmB,oBAAoB,QAAQ,UAAU,WAAW,OAAO,IAAI,IAAI,QAAQ,CAAC;AACxG,UAAM,SAAS,sBAAsB,EAAE;AACvC,UAAM,4BAA4B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,UAAM,6BAA6B;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAEA,QAAI,OAAO,WAAW,UAAU;AAC5B,YAAM,eAAe,KAAK,wBAAwB,gCAAgC;AAAA,IACtF;AAEA,UAAM,SAAS,MAAM,cAAc;AAAA,MAC/B,IAAI;AAAA,MACJ,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB;AAAA,MACA,UAAU,qBAAqB,QAAQ;AAAA,MACvC,WAAW,sBAAsB,SAAS;AAAA,MAC1C,QAAQ,2BAA2B,MAAM;AAAA,IAC7C,CAAC;AAED,QAAI,OAAO,WAAW,WAAW;AAC7B,gBAAU;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,OAAO,KAAK;AAAA,QACpB,WAAW,OAAO,KAAK;AAAA,MAC3B,CAAC;AAAA,IACL;AAEA,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,qCAAqC,CAC9C,iBAAiB,oBACjB,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,IAAI,mBAAmB,oBAAoB,QAAQ,WAAW,WAAW,WAAW,OAAO,IAC/F,IAAI,QAAQ,CAAC;AACjB,UAAM,SAAS,sBAAsB,EAAE;AACvC,UAAM,4BAA4B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,UAAM,6BAA6B;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAEA,QAAI,OAAO,WAAW,UAAU;AAC5B,YAAM,eAAe,KAAK,yBAAyB,iCAAiC;AAAA,IACxF;AAEA,QAAI,OAAO,cAAc,UAAU;AAC/B,YAAM,eAAe,KAAK,4BAA4B,oCAAoC;AAAA,IAC9F;AAEA,UAAM,SAAS,MAAM,eAAe;AAAA,MAChC,IAAI;AAAA,MACJ,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB;AAAA,MACA;AAAA,MACA,WAAW,uBAAuB,SAAS;AAAA,MAC3C,WAAW,iBAAiB,SAAS;AAAA,MACrC,QAAQ,2BAA2B,MAAM;AAAA,IAC7C,CAAC;AAED,QAAI,OAAO,WAAW,WAAW;AAC7B,gBAAU;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,OAAO,KAAK;AAAA,QACpB,WAAW,OAAO,KAAK;AAAA,MAC3B,CAAC;AAAA,IACL;AAEA,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,sCAAsC,CAC/C,kBAAkB,qBAClB,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,IAAI,mBAAmB,oBAAoB,QAAQ,aAAa,OAAO,IAAI,IAAI,QAAQ,CAAC;AAChG,UAAM,SAAS,sBAAsB,EAAE;AACvC,UAAM,4BAA4B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,UAAM,6BAA6B;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAEA,QAAI,OAAO,WAAW,UAAU;AAC5B,YAAM,eAAe,KAAK,0BAA0B,kCAAkC;AAAA,IAC1F;AAEA,QAAI,OAAO,gBAAgB,UAAU;AACjC,YAAM,eAAe,KAAK,4BAA4B,wCAAwC;AAAA,IAClG;AAEA,UAAM,SAAS,MAAM,gBAAgB;AAAA,MACjC,IAAI;AAAA,MACJ,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB;AAAA,MACA;AAAA,MACA,QAAQ,2BAA2B,MAAM;AAAA,IAC7C,CAAC;AAED,QAAI,OAAO,WAAW,WAAW;AAC7B,gBAAU;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,OAAO,KAAK;AAAA,QACpB,WAAW,OAAO,KAAK;AAAA,MAC3B,CAAC;AAAA,IACL;AAEA,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,qCAAqC,CAC9C,iBAAiB,oBACjB,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,IAAI,mBAAmB,OAAO,QAAQ,WAAW,IAAI,IAAI,QAAQ,CAAC;AAC1E,UAAM,SAAS,sBAAsB,EAAE;AACvC,UAAM,iBAAiB,kBAAkB,MAAM;AAC/C,UAAM,qBAAqB,6BAA6B,UAAU;AAElE,QAAI,OAAO,sBAAsB,UAAU;AACvC,YAAM,eAAe,KAAK,wBAAwB,qCAAqC;AAAA,IAC3F;AAEA,QAAI,UAAU,UAAa,OAAO,UAAU,UAAU;AAClD,YAAM,eAAe,KAAK,sBAAsB,8BAA8B;AAAA,IAClF;AAEA,QAAI,WAAW,UAAa,mBAAmB,MAAM;AACjD,YAAM,eAAe,KAAK,uBAAuB,mDAAmD;AAAA,IACxG;AAEA,QAAI,UAAU,UAAa,WAAW,UAAa,uBAAuB,QAAW;AACjF,YAAM,eAAe,KAAK,sBAAsB,+CAA+C;AAAA,IACnG;AAEA,UAAM,SAAS,MAAM,eAAe;AAAA,MAChC,IAAI;AAAA,MACJ;AAAA,MACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,MACvC,GAAI,iBAAiB,EAAE,QAAQ,eAAe,IAAI,CAAC;AAAA,MACnD,GAAI,uBAAuB,SAAY,EAAE,YAAY,mBAAmB,IAAI,CAAC;AAAA,IACjF,CAAC;AAED,QAAI,OAAO,WAAW,WAAW;AAC7B,gBAAU;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,OAAO,KAAK;AAAA,QACpB,WAAW,OAAO,KAAK;AAAA,MAC3B,CAAC;AAAA,IACL;AAEA,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,6BAA6B,CACtC,aAAa,gBACb,YAA6B,oBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,KAAK,OAAO,IAAI,MAAM,EAAE;AAE9B,QAAI,CAAC,OAAO,UAAU,EAAE,KAAK,MAAM,GAAG;AAClC,YAAM,eAAe,KAAK,mBAAmB,8BAA8B;AAAA,IAC/E;AAEA,UAAM,cAAc,MAAM,WAAW,EAAE;AAEvC,QAAI,CAAC,aAAa;AACd,YAAM,eAAe,KAAK,kBAAkB,mCAAmC;AAAA,IACnF;AAEA,cAAU;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,YAAY;AAAA,IACxB,CAAC;AAED,QAAI,OAAO,GAAG,EACT,KAAK;AAAA,MACF,SAAS;AAAA,MACT,MAAM;AAAA,IACV,CAAC,EACA,IAAI;AAAA,EACb;AACJ;","names":[]}
@@ -1 +0,0 @@
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 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/services/cleanup.ts"],"sourcesContent":["import models from '~/models.js';\nimport { contentReferencesNote } from './content-blocks.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,\n forceReasons: candidate.forceReasons,\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\n .findMany({\n select: {\n id: true,\n title: true,\n content: true,\n },\n where: {\n NOT: { id: noteId },\n },\n orderBy: [{ pinned: 'desc' }, { updatedAt: 'desc' }],\n })\n .then((notes) => notes.filter((note) => contentReferencesNote(note.content, noteId))),\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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/features/note/services/content-blocks.ts"],"sourcesContent":["import { parseBlockNoteContent, walkBlockNoteTree } from '~/modules/blocknote-tree.js';\n\ninterface BlockNote<T = unknown> {\n id?: string;\n type?: string;\n props?: T;\n content?: unknown;\n children?: unknown;\n}\n\ninterface ReferenceProps {\n id?: string | number;\n title?: string;\n}\n\ninterface NoteGraphInput {\n id: string | number;\n title: string;\n content: string;\n}\n\nexport interface NoteGraphResult {\n nodes: Array<{ id: string; title: string; connections: number }>;\n links: Array<{ source: string; target: string }>;\n}\n\nexport const parseNoteContent = (content: string): unknown[] | null => {\n return parseBlockNoteContent(content);\n};\n\nexport const normalizeReferenceId = (id: unknown) => {\n if (typeof id === 'number') {\n return Number.isFinite(id) ? String(id) : null;\n }\n\n if (typeof id !== 'string') {\n return null;\n }\n\n const normalizedId = id.trim();\n\n return normalizedId.length > 0 ? normalizedId : null;\n};\n\nexport const extractBlocksByType = <T>(type: string, dataArray: unknown): BlockNote<T>[] => {\n const result: BlockNote<T>[] = [];\n\n walkBlockNoteTree(dataArray, (node) => {\n if (node.type === type) {\n result.push(node as BlockNote<T>);\n }\n });\n\n return result;\n};\n\nexport const extractReferenceBlocks = (dataArray: unknown) => {\n return extractBlocksByType<ReferenceProps>('reference', dataArray).filter((block) => {\n return normalizeReferenceId(block.props?.id) !== null;\n });\n};\n\nexport const extractReferenceBlocksFromContent = (content: string) => {\n const parsed = parseNoteContent(content);\n return parsed ? extractReferenceBlocks(parsed) : [];\n};\n\nexport const contentReferencesNote = (content: string, noteId: string | number) => {\n const targetId = normalizeReferenceId(noteId);\n\n if (!targetId) {\n return false;\n }\n\n return extractReferenceBlocksFromContent(content).some(\n (block) => normalizeReferenceId(block.props?.id) === targetId,\n );\n};\n\nexport const syncReferenceTitlesInContent = (content: string, titlesById: Map<string, string>) => {\n const parsed = parseNoteContent(content);\n\n if (!parsed) {\n return null;\n }\n\n let changed = false;\n\n for (const block of extractReferenceBlocks(parsed)) {\n const props = block.props;\n const id = normalizeReferenceId(props?.id);\n\n if (!props || id === null) {\n continue;\n }\n\n const title = titlesById.get(id);\n\n if (title !== undefined && props.title !== title) {\n block.props = {\n ...props,\n title,\n };\n changed = true;\n }\n }\n\n return changed ? JSON.stringify(parsed) : content;\n};\n\nexport const buildNoteGraph = (notes: NoteGraphInput[]): NoteGraphResult => {\n const existingNoteIds = new Set(notes.map((note) => String(note.id)));\n const links: NoteGraphResult['links'] = [];\n const connectionCount: Record<string, number> = {};\n const linkSet = new Set<string>();\n\n for (const note of notes) {\n const sourceId = String(note.id);\n\n for (const block of extractReferenceBlocksFromContent(note.content)) {\n const targetId = normalizeReferenceId(block.props?.id);\n\n if (!targetId || sourceId === targetId || !existingNoteIds.has(targetId)) {\n continue;\n }\n\n const linkKey = `${sourceId}-${targetId}`;\n const reverseLinkKey = `${targetId}-${sourceId}`;\n\n if (linkSet.has(linkKey) || linkSet.has(reverseLinkKey)) {\n continue;\n }\n\n linkSet.add(linkKey);\n links.push({\n source: sourceId,\n target: targetId,\n });\n connectionCount[sourceId] = (connectionCount[sourceId] || 0) + 1;\n connectionCount[targetId] = (connectionCount[targetId] || 0) + 1;\n }\n }\n\n return {\n nodes: notes.map((note) => {\n const id = String(note.id);\n\n return {\n id,\n title: note.title || 'Untitled',\n connections: connectionCount[id] || 0,\n };\n }),\n links,\n };\n};\n"],"mappings":"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","names":[]}