ocean-brain 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/package.json +5 -5
  2. package/scripts/postinstall-prisma.mjs +3 -2
  3. package/server/client/dist/assets/Calendar-CSXEuWmQ.js +25 -0
  4. package/server/client/dist/assets/Callout-Ap1OcxWN.js +1 -0
  5. package/server/client/dist/assets/Graph-C0Ax9qjI.js +1 -0
  6. package/server/client/dist/assets/{Image-ZxF459sC.js → Image-CSbcG-5a.js} +1 -1
  7. package/server/client/dist/assets/{Image.es-BQfyKc2-.js → Image.es-Fbe7aDDo.js} +1 -1
  8. package/server/client/dist/assets/{ModalActionRow-DyOb58KJ.js → ModalActionRow-DjI5b4bl.js} +1 -1
  9. package/server/client/dist/assets/Note-BY6WgYLq.js +1 -0
  10. package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
  11. package/server/client/dist/assets/Reminders-vRksvSQE.js +1 -0
  12. package/server/client/dist/assets/Search-Cq4tuiJO.js +1 -0
  13. package/server/client/dist/assets/{SurfaceCard-nZNf9lyE.js → SurfaceCard-Br6TjX5W.js} +1 -1
  14. package/server/client/dist/assets/Tag-ARDLA-Pb.js +1 -0
  15. package/server/client/dist/assets/TagNotes-CDGVrCJU.js +1 -0
  16. package/server/client/dist/assets/{Trash.es-BGoN5q9L.js → Trash.es-BfCEiL3-.js} +1 -1
  17. package/server/client/dist/assets/ViewNotes-ZBOQ1adH.js +1 -0
  18. package/server/client/dist/assets/Views-DQHPfdwM.js +1 -0
  19. package/server/client/dist/assets/{image.api-0mMkElZk.js → image.api-DGXlQ_Ca.js} +1 -1
  20. package/server/client/dist/assets/index-B28TO4FC.js +1 -0
  21. package/server/client/dist/assets/{index-DL7pUwq2.css → index-Bm9l_L20.css} +1 -1
  22. package/server/client/dist/assets/index-Bsx4P7EA.js +210 -0
  23. package/server/client/dist/assets/manage-image-Dg297A_h.js +1 -0
  24. package/server/client/dist/assets/manage-image-detail-hk6jWyiB.js +1 -0
  25. package/server/client/dist/assets/mcp-CSjmBjFR.js +1 -0
  26. package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
  27. package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-5N5mRkIT.js} +3 -3
  28. package/server/client/dist/assets/{placeholder-i-uNe71U.js → placeholder-Bmiowo28.js} +4 -4
  29. package/server/client/dist/assets/trash-CM7SbVtW.js +1 -0
  30. package/server/client/dist/assets/{useReminderMutate-tNMwwRnx.js → useReminderMutate-DsIvVpCQ.js} +2 -2
  31. package/server/client/dist/assets/view-dashboard-CP8nhfxQ.js +90 -0
  32. package/server/client/dist/index.html +4 -4
  33. package/server/dist/app.js +8 -84
  34. package/server/dist/app.js.map +1 -1
  35. package/server/dist/features/auth/http/api.js +43 -0
  36. package/server/dist/features/auth/http/api.js.map +1 -0
  37. package/server/dist/features/auth/http/login-page.js +28 -0
  38. package/server/dist/features/auth/http/login-page.js.map +1 -0
  39. package/server/dist/features/auth/http/pages.js +50 -0
  40. package/server/dist/features/auth/http/pages.js.map +1 -0
  41. package/server/dist/features/auth/service.js +94 -0
  42. package/server/dist/features/auth/service.js.map +1 -0
  43. package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
  44. package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
  45. package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
  46. package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
  47. package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
  48. package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
  49. package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
  50. package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
  51. package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
  52. package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
  53. package/server/dist/features/cache/graphql/index.js +21 -0
  54. package/server/dist/features/cache/graphql/index.js.map +1 -0
  55. package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
  56. package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
  57. package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
  58. package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
  59. package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
  60. package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
  61. package/server/dist/features/image/graphql/image.resolver.js +12 -0
  62. package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
  63. package/server/dist/features/image/graphql/image.type-defs.js +31 -0
  64. package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
  65. package/server/dist/features/image/graphql/index.js +16 -0
  66. package/server/dist/features/image/graphql/index.js.map +1 -0
  67. package/server/dist/features/image/http/upload.js +48 -0
  68. package/server/dist/features/image/http/upload.js.map +1 -0
  69. package/server/dist/features/image/services/delete.js +52 -0
  70. package/server/dist/features/image/services/delete.js.map +1 -0
  71. package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
  72. package/server/dist/features/image/services/remote-fetch.js.map +1 -0
  73. package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
  74. package/server/dist/features/image/services/upload.js.map +1 -0
  75. package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
  76. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
  77. package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
  78. package/server/dist/features/mcp-admin/service.js.map +1 -0
  79. package/server/dist/features/note/graphql/index.js +8 -0
  80. package/server/dist/features/note/graphql/index.js.map +1 -0
  81. package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
  82. package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
  83. package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
  84. package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
  85. package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
  86. package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
  87. package/server/dist/features/note/graphql/note.query.resolver.js +382 -0
  88. package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
  89. package/server/dist/features/note/graphql/note.resolver.js +14 -0
  90. package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
  91. package/server/dist/features/note/graphql/note.type-defs.js +184 -0
  92. package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
  93. package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
  94. package/server/dist/features/note/http/mcp.js.map +1 -0
  95. package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
  96. package/server/dist/features/note/services/authoring.js.map +1 -0
  97. package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
  98. package/server/dist/features/note/services/cleanup.js.map +1 -0
  99. package/server/dist/features/note/services/search-backfill.js +84 -0
  100. package/server/dist/features/note/services/search-backfill.js.map +1 -0
  101. package/server/dist/features/note/services/search.js +197 -0
  102. package/server/dist/features/note/services/search.js.map +1 -0
  103. package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
  104. package/server/dist/features/note/services/snapshot.js.map +1 -0
  105. package/server/dist/{modules/note-tag-filter.js → features/note/services/tag-filter.js} +2 -6
  106. package/server/dist/features/note/services/tag-filter.js.map +1 -0
  107. package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +28 -23
  108. package/server/dist/features/note/services/trash.js.map +1 -0
  109. package/server/dist/features/placeholder/graphql/index.js +27 -0
  110. package/server/dist/features/placeholder/graphql/index.js.map +1 -0
  111. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
  112. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
  113. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
  114. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
  115. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
  116. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
  117. package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
  118. package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
  119. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
  120. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
  121. package/server/dist/features/reminder/graphql/index.js +27 -0
  122. package/server/dist/features/reminder/graphql/index.js.map +1 -0
  123. package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
  124. package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
  125. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
  126. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
  127. package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
  128. package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
  129. package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
  130. package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
  131. package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
  132. package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
  133. package/server/dist/features/tag/graphql/index.js +14 -0
  134. package/server/dist/features/tag/graphql/index.js.map +1 -0
  135. package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
  136. package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
  137. package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
  138. package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
  139. package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
  140. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
  141. package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
  142. package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
  143. package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
  144. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
  145. package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
  146. package/server/dist/features/tag/http/mcp.js.map +1 -0
  147. package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
  148. package/server/dist/features/tag/services/organization.js.map +1 -0
  149. package/server/dist/features/view/graphql/index.js +11 -0
  150. package/server/dist/features/view/graphql/index.js.map +1 -0
  151. package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
  152. package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
  153. package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
  154. package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
  155. package/server/dist/features/view/graphql/view.resolver.js +10 -0
  156. package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
  157. package/server/dist/features/view/graphql/view.type-defs.js +63 -0
  158. package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
  159. package/server/dist/features/view/services/workspace.js +432 -0
  160. package/server/dist/features/view/services/workspace.js.map +1 -0
  161. package/server/dist/main.js +14 -0
  162. package/server/dist/main.js.map +1 -1
  163. package/server/dist/modules/auth-guard.js +10 -9
  164. package/server/dist/modules/auth-guard.js.map +1 -1
  165. package/server/dist/modules/auth-mode.js +1 -3
  166. package/server/dist/modules/auth-mode.js.map +1 -1
  167. package/server/dist/modules/blocknote.js +8 -17
  168. package/server/dist/modules/blocknote.js.map +1 -1
  169. package/server/dist/modules/data-maintenance.js +75 -0
  170. package/server/dist/modules/data-maintenance.js.map +1 -0
  171. package/server/dist/modules/error-handler.js.map +1 -1
  172. package/server/dist/modules/logger.js +5 -23
  173. package/server/dist/modules/logger.js.map +1 -1
  174. package/server/dist/modules/mcp-auth.js +4 -5
  175. package/server/dist/modules/mcp-auth.js.map +1 -1
  176. package/server/dist/modules/mcp-token.js.map +1 -1
  177. package/server/dist/modules/prisma-runtime.js +2 -7
  178. package/server/dist/modules/prisma-runtime.js.map +1 -1
  179. package/server/dist/modules/recovery-retention.js +1 -1
  180. package/server/dist/modules/recovery-retention.js.map +1 -1
  181. package/server/dist/modules/server-events-handler.js +34 -0
  182. package/server/dist/modules/server-events-handler.js.map +1 -0
  183. package/server/dist/modules/server-events.js +32 -0
  184. package/server/dist/modules/server-events.js.map +1 -0
  185. package/server/dist/modules/use-async.js.map +1 -1
  186. package/server/dist/paths.js.map +1 -1
  187. package/server/dist/routes/api.js +34 -0
  188. package/server/dist/routes/api.js.map +1 -0
  189. package/server/dist/routes/auth-pages.js +11 -0
  190. package/server/dist/routes/auth-pages.js.map +1 -0
  191. package/server/dist/routes/client.js +27 -0
  192. package/server/dist/routes/client.js.map +1 -0
  193. package/server/dist/routes/graphql.js +33 -0
  194. package/server/dist/routes/graphql.js.map +1 -0
  195. package/server/dist/routes/index.js +11 -0
  196. package/server/dist/routes/index.js.map +1 -0
  197. package/server/dist/routes/mcp.js +30 -0
  198. package/server/dist/routes/mcp.js.map +1 -0
  199. package/server/dist/schema/index.js +11 -8
  200. package/server/dist/schema/index.js.map +1 -1
  201. package/server/dist/types/index.js.map +1 -1
  202. package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
  203. package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
  204. package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
  205. package/server/prisma/schema.prisma +66 -12
  206. package/server/client/dist/assets/Calendar-Kjy1PX6L.js +0 -25
  207. package/server/client/dist/assets/Graph-Bl5K2qUw.js +0 -1
  208. package/server/client/dist/assets/Note-BfGma2Qc.css +0 -1
  209. package/server/client/dist/assets/Note-DN_ko2M3.js +0 -1
  210. package/server/client/dist/assets/Reminders-BK-Uv9Ps.js +0 -1
  211. package/server/client/dist/assets/Search-ClHeWvmI.js +0 -1
  212. package/server/client/dist/assets/Tag-Dph4V_kl.js +0 -1
  213. package/server/client/dist/assets/TagNotes-CxP5qHZj.js +0 -1
  214. package/server/client/dist/assets/index-6lIw6jYW.js +0 -1
  215. package/server/client/dist/assets/index-Ba2XPRI8.js +0 -205
  216. package/server/client/dist/assets/manage-image-Br_kKxbb.js +0 -1
  217. package/server/client/dist/assets/manage-image-detail-9DzqbiqI.js +0 -1
  218. package/server/client/dist/assets/mcp-C_So91eX.js +0 -1
  219. package/server/client/dist/assets/trash-Cg3nJ1m5.js +0 -1
  220. package/server/dist/modules/auth.js +0 -25
  221. package/server/dist/modules/auth.js.map +0 -1
  222. package/server/dist/modules/image-upload.js.map +0 -1
  223. package/server/dist/modules/mcp-admin.js.map +0 -1
  224. package/server/dist/modules/note-authoring.js.map +0 -1
  225. package/server/dist/modules/note-cleanup.js.map +0 -1
  226. package/server/dist/modules/note-snapshot.js.map +0 -1
  227. package/server/dist/modules/note-tag-filter.js.map +0 -1
  228. package/server/dist/modules/note-trash.js.map +0 -1
  229. package/server/dist/modules/remote-image.js.map +0 -1
  230. package/server/dist/modules/tag-organization.js.map +0 -1
  231. package/server/dist/schema/cache/index.js +0 -58
  232. package/server/dist/schema/cache/index.js.map +0 -1
  233. package/server/dist/schema/image/index.js +0 -88
  234. package/server/dist/schema/image/index.js.map +0 -1
  235. package/server/dist/schema/note/index.js +0 -582
  236. package/server/dist/schema/note/index.js.map +0 -1
  237. package/server/dist/schema/placeholder/index.js +0 -105
  238. package/server/dist/schema/placeholder/index.js.map +0 -1
  239. package/server/dist/schema/reminder/index.js +0 -161
  240. package/server/dist/schema/reminder/index.js.map +0 -1
  241. package/server/dist/schema/tag/index.js +0 -81
  242. package/server/dist/schema/tag/index.js.map +0 -1
  243. package/server/dist/urls.js +0 -9
  244. package/server/dist/urls.js.map +0 -1
  245. package/server/dist/views/auth.js +0 -305
  246. package/server/dist/views/auth.js.map +0 -1
  247. package/server/dist/views/image.js +0 -44
  248. package/server/dist/views/image.js.map +0 -1
  249. package/server/dist/views/index.js +0 -6
  250. package/server/dist/views/index.js.map +0 -1
  251. package/server/dist/views/mcp-admin.js.map +0 -1
  252. package/server/dist/views/note.js.map +0 -1
  253. package/server/dist/views/tag.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/blocknote.ts"],"sourcesContent":["import { ServerBlockNoteEditor } from '@blocknote/server-util';\n\nimport models from '~/models.js';\nimport { ensureTagByName } from './tag-organization.js';\n\ninterface BlockNote {\n id?: string;\n type: string;\n props?: Record<string, unknown>;\n content?: BlockNoteContent;\n children?: BlockNote[];\n text?: string;\n styles?: Record<string, unknown>;\n}\n\ninterface BlockNoteInline {\n type: string;\n props?: Record<string, unknown>;\n text?: string;\n styles?: Record<string, unknown>;\n}\n\ninterface BlockNoteTableCell {\n type: 'tableCell';\n props?: Record<string, unknown>;\n content?: BlockNoteInline[];\n}\n\ninterface BlockNoteTableRow {\n cells?: BlockNoteTableCell[];\n}\n\ninterface BlockNoteTableContent {\n type: 'tableContent';\n columnWidths?: Array<number | null>;\n headerRows?: number;\n rows?: BlockNoteTableRow[];\n}\n\ntype BlockNoteContent = BlockNoteInline[] | BlockNoteTableContent;\n\ninterface MarkdownImportDeps {\n ensureTag: (name: string) => Promise<\n | {\n id: string;\n name: string;\n }\n | {\n tag: {\n id: string;\n name: string;\n };\n }\n >;\n findNotesByTitle: (title: string) => Promise<Array<{\n id: string;\n title: string;\n }>>;\n}\n\nconst UNSUPPORTED_MARKDOWN_BLOCK_TYPES = new Set([\n 'tableOfContents'\n]);\nconst TAG_PLACEHOLDER_PREFIX = 'OCEAN_BRAIN_TAG_';\nconst TAG_PLACEHOLDER_SUFFIX = '_TOKEN';\n\nconst defaultMarkdownImportDeps: MarkdownImportDeps = {\n ensureTag: async (name) => ensureTagByName(name),\n findNotesByTitle: async (title) => {\n const notes = await models.note.findMany({\n select: {\n id: true,\n title: true\n },\n where: { title },\n orderBy: { createdAt: 'asc' },\n take: 2\n });\n\n return notes.map((note) => ({\n id: String(note.id),\n title: note.title\n }));\n }\n};\n\nfunction stripUnsupportedMarkdownBlocks(blocks: BlockNote[]): BlockNote[] {\n return blocks\n .filter((block) => !UNSUPPORTED_MARKDOWN_BLOCK_TYPES.has(block.type))\n .map((block) => ({\n ...block,\n children: block.children?.length\n ? stripUnsupportedMarkdownBlocks(block.children)\n : []\n }));\n}\n\nfunction createTagPlaceholder(index: number) {\n return `${TAG_PLACEHOLDER_PREFIX}${index}${TAG_PLACEHOLDER_SUFFIX}`;\n}\n\nfunction isTableContent(content: BlockNoteContent | undefined): content is BlockNoteTableContent {\n return !Array.isArray(content) && content?.type === 'tableContent';\n}\n\nfunction mapBlockContent(\n content: BlockNoteContent | undefined,\n mapInline: (inline: BlockNoteInline) => BlockNoteInline\n): BlockNoteContent | undefined {\n if (Array.isArray(content)) {\n return content.map(mapInline);\n }\n\n if (!isTableContent(content)) {\n return content;\n }\n\n return {\n ...content,\n rows: content.rows?.map((row) => ({\n ...row,\n cells: row.cells?.map((cell) => ({\n ...cell,\n content: cell.content?.map(mapInline)\n }))\n }))\n };\n}\n\nasync function mapBlockContentAsync(\n content: BlockNoteContent | undefined,\n mapInline: (inline: BlockNoteInline) => Promise<BlockNoteInline[]>\n): Promise<BlockNoteContent | undefined> {\n if (Array.isArray(content)) {\n return (await Promise.all(content.map(mapInline))).flat();\n }\n\n if (!isTableContent(content)) {\n return content;\n }\n\n return {\n ...content,\n rows: await Promise.all(\n (content.rows ?? []).map(async (row) => ({\n ...row,\n cells: await Promise.all(\n (row.cells ?? []).map(async (cell) => ({\n ...cell,\n content: cell.content\n ? (await Promise.all(cell.content.map(mapInline))).flat()\n : cell.content\n }))\n )\n }))\n )\n };\n}\n\nfunction visitBlockContent(\n content: BlockNoteContent | undefined,\n visitInline: (inline: BlockNoteInline) => void\n) {\n if (Array.isArray(content)) {\n for (const inline of content) {\n visitInline(inline);\n }\n return;\n }\n\n if (!isTableContent(content)) {\n return;\n }\n\n for (const row of content.rows ?? []) {\n for (const cell of row.cells ?? []) {\n for (const inline of cell.content ?? []) {\n visitInline(inline);\n }\n }\n }\n}\n\nfunction mapBlocks(\n blocks: BlockNote[],\n mapContent: (content: BlockNoteContent | undefined) => BlockNoteContent | undefined\n): BlockNote[] {\n return blocks.map((block) => ({\n ...block,\n content: mapContent(block.content),\n children: block.children?.length\n ? mapBlocks(block.children, mapContent)\n : []\n }));\n}\n\nasync function mapBlocksAsync(\n blocks: BlockNote[],\n mapContent: (content: BlockNoteContent | undefined) => Promise<BlockNoteContent | undefined>\n): Promise<BlockNote[]> {\n return Promise.all(\n blocks.map(async (block) => ({\n ...block,\n content: await mapContent(block.content),\n children: block.children?.length\n ? await mapBlocksAsync(block.children, mapContent)\n : []\n }))\n );\n}\n\nfunction preprocessCustomInlineContent(\n blocks: BlockNote[],\n placeholderToTag: Map<string, string>,\n nextPlaceholderIndex: { value: number }\n): BlockNote[] {\n return mapBlocks(\n blocks,\n (content) => mapBlockContent(content, (inline) => {\n if (inline.type === 'reference') {\n return {\n type: 'text',\n text: `[[${inline.props?.title || inline.props?.id || ''}]]`,\n styles: {}\n };\n }\n if (inline.type === 'tag') {\n const tag = (inline.props?.tag as string) || '';\n const placeholder = createTagPlaceholder(nextPlaceholderIndex.value++);\n placeholderToTag.set(placeholder, tag);\n return {\n type: 'text',\n text: placeholder,\n styles: {}\n };\n }\n return inline;\n })\n );\n}\n\nfunction restoreTagPlaceholdersInMarkdown(markdown: string, placeholderToTag: Map<string, string>) {\n let restoredMarkdown = markdown;\n\n for (const [placeholder, tag] of placeholderToTag.entries()) {\n restoredMarkdown = restoredMarkdown.split(placeholder).join(`[${tag}]`);\n }\n\n return restoredMarkdown;\n}\n\nfunction preprocessMarkdownExplicitTags(markdown: string) {\n const placeholderToTag = new Map<string, string>();\n let nextPlaceholderIndex = 0;\n const preprocessedMarkdown = markdown.replace(/\\[([@#][^\\s[\\]]+)\\]/g, (_match, tagToken: string) => {\n const placeholder = createTagPlaceholder(nextPlaceholderIndex++);\n placeholderToTag.set(placeholder, tagToken);\n return placeholder;\n });\n\n return {\n markdown: preprocessedMarkdown,\n placeholderToTag\n };\n}\n\nfunction findTagPlaceholderAtCursor(\n text: string,\n cursor: number,\n placeholderToTag: Map<string, string>\n) {\n for (const [placeholder, tagToken] of placeholderToTag.entries()) {\n if (text.startsWith(placeholder, cursor)) {\n return {\n placeholder,\n tagToken\n };\n }\n }\n\n return null;\n}\n\nfunction restoreRemainingTagPlaceholders(\n blocks: BlockNote[],\n placeholderToTag: Map<string, string>\n): BlockNote[] {\n return mapBlocks(\n blocks,\n (content) => mapBlockContent(content, (inline) => {\n if (inline.type !== 'text' || typeof inline.text !== 'string') {\n return inline;\n }\n\n let text = inline.text;\n\n for (const [placeholder, tagToken] of placeholderToTag.entries()) {\n text = text.split(placeholder).join(`[${tagToken}]`);\n }\n\n return {\n ...inline,\n text\n };\n })\n );\n}\n\nfunction createTextInline(text: string, styles: Record<string, unknown> = {}): BlockNoteInline {\n return {\n type: 'text',\n text,\n styles\n };\n}\n\nfunction appendInline(target: BlockNoteInline[], inline: BlockNoteInline) {\n const previous = target.at(-1);\n\n if (\n previous?.type === 'text' &&\n inline.type === 'text' &&\n previous.text !== undefined &&\n inline.text !== undefined &&\n JSON.stringify(previous.styles ?? {}) === JSON.stringify(inline.styles ?? {})\n ) {\n previous.text += inline.text;\n return;\n }\n\n target.push(inline);\n}\n\nasync function restoreCustomInlineContent(\n blocks: BlockNote[],\n deps: MarkdownImportDeps,\n placeholderToTag: Map<string, string>\n): Promise<BlockNote[]> {\n const tagCache = new Map<string, Promise<{ id: string; tag: string }>>();\n const noteCache = new Map<string, Promise<Array<{ id: string; title: string }>>>();\n\n const getTag = (token: string) => {\n const normalizedToken = token.startsWith('#')\n ? `@${token.slice(1)}`\n : token;\n let existing = tagCache.get(normalizedToken);\n\n if (!existing) {\n existing = deps.ensureTag(normalizedToken.slice(1)).then((result) => ({\n id: 'tag' in result ? result.tag.id : result.id,\n tag: 'tag' in result ? result.tag.name : result.name\n }));\n tagCache.set(normalizedToken, existing);\n }\n\n return existing;\n };\n\n const getNotesByTitle = (title: string) => {\n let existing = noteCache.get(title);\n\n if (!existing) {\n existing = deps.findNotesByTitle(title);\n noteCache.set(title, existing);\n }\n\n return existing;\n };\n\n const restoreTextInline = async (inline: BlockNoteInline): Promise<BlockNoteInline[]> => {\n if (\n inline.type !== 'text' ||\n typeof inline.text !== 'string' ||\n !inline.text ||\n inline.styles?.code === true\n ) {\n return [inline];\n }\n\n const restored: BlockNoteInline[] = [];\n const styles = inline.styles ?? {};\n let cursor = 0;\n\n while (cursor < inline.text.length) {\n if (inline.text.startsWith('[[', cursor)) {\n const closeIndex = inline.text.indexOf(']]', cursor + 2);\n\n if (closeIndex !== -1) {\n const title = inline.text.slice(cursor + 2, closeIndex);\n const matchingNotes = await getNotesByTitle(title);\n\n if (matchingNotes.length === 1) {\n appendInline(restored, {\n type: 'reference',\n props: {\n id: matchingNotes[0].id,\n title: matchingNotes[0].title\n }\n });\n cursor = closeIndex + 2;\n continue;\n }\n }\n }\n\n const tagPlaceholderMatch = findTagPlaceholderAtCursor(inline.text, cursor, placeholderToTag);\n\n if (tagPlaceholderMatch) {\n const tag = await getTag(tagPlaceholderMatch.tagToken);\n appendInline(restored, {\n type: 'tag',\n props: tag\n });\n cursor += tagPlaceholderMatch.placeholder.length;\n continue;\n }\n\n let nextCursor = inline.text.length;\n\n const nextReferenceCursor = inline.text.indexOf('[[', cursor + 1);\n\n if (nextReferenceCursor !== -1) {\n nextCursor = Math.min(nextCursor, nextReferenceCursor);\n }\n\n for (const placeholder of placeholderToTag.keys()) {\n const nextPlaceholderCursor = inline.text.indexOf(placeholder, cursor + 1);\n\n if (nextPlaceholderCursor !== -1) {\n nextCursor = Math.min(nextCursor, nextPlaceholderCursor);\n }\n }\n\n appendInline(restored, createTextInline(inline.text.slice(cursor, nextCursor), styles));\n cursor = nextCursor;\n }\n\n return restored;\n };\n\n return mapBlocksAsync(\n blocks,\n (content) => mapBlockContentAsync(content, restoreTextInline)\n );\n}\n\nfunction collectTagIds(blocks: BlockNote[]): string[] {\n const tagIds = new Set<string>();\n\n const visit = (items: BlockNote[]) => {\n for (const block of items) {\n visitBlockContent(block.content, (inline) => {\n if (inline.type === 'tag' && typeof inline.props?.id === 'string') {\n tagIds.add(inline.props.id);\n }\n });\n\n if (block.children?.length) {\n visit(block.children);\n }\n }\n };\n\n visit(blocks);\n return [...tagIds];\n}\n\nlet editorInstance: ServerBlockNoteEditor | null = null;\n\nfunction getEditor(): ServerBlockNoteEditor {\n if (!editorInstance) {\n editorInstance = ServerBlockNoteEditor.create();\n }\n return editorInstance;\n}\n\nexport async function blocksToMarkdown(contentJson: string): Promise<string> {\n try {\n const blocks = JSON.parse(contentJson);\n const supportedBlocks = stripUnsupportedMarkdownBlocks(blocks);\n const placeholderToTag = new Map<string, string>();\n const processed = preprocessCustomInlineContent(\n supportedBlocks,\n placeholderToTag,\n { value: 0 }\n );\n const editor = getEditor();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const markdown = await editor.blocksToMarkdownLossy(processed as any);\n return restoreTagPlaceholdersInMarkdown(markdown, placeholderToTag);\n } catch {\n return '';\n }\n}\n\nexport async function markdownToBlocksJson(\n markdown: string,\n deps: MarkdownImportDeps = defaultMarkdownImportDeps\n): Promise<string> {\n const editor = getEditor();\n const preprocessedMarkdown = preprocessMarkdownExplicitTags(markdown);\n const blocks = await editor.tryParseMarkdownToBlocks(preprocessedMarkdown.markdown);\n const restoredBlocks = await restoreCustomInlineContent(\n blocks as BlockNote[],\n deps,\n preprocessedMarkdown.placeholderToTag\n );\n return JSON.stringify(\n restoreRemainingTagPlaceholders(restoredBlocks, preprocessedMarkdown.placeholderToTag)\n );\n}\n\nexport function extractTagIdsFromContentJson(contentJson: string): string[] {\n const blocks = JSON.parse(contentJson) as BlockNote[];\n return collectTagIds(blocks);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/modules/blocknote.ts"],"sourcesContent":["import { ServerBlockNoteEditor } from '@blocknote/server-util';\nimport { ensureTagByName } from '~/features/tag/services/organization.js';\nimport models from '~/models.js';\n\ninterface BlockNote {\n id?: string;\n type: string;\n props?: Record<string, unknown>;\n content?: BlockNoteContent;\n children?: BlockNote[];\n text?: string;\n styles?: Record<string, unknown>;\n}\n\ninterface BlockNoteInline {\n type: string;\n props?: Record<string, unknown>;\n text?: string;\n styles?: Record<string, unknown>;\n}\n\ninterface BlockNoteTableCell {\n type: 'tableCell';\n props?: Record<string, unknown>;\n content?: BlockNoteInline[];\n}\n\ninterface BlockNoteTableRow {\n cells?: BlockNoteTableCell[];\n}\n\ninterface BlockNoteTableContent {\n type: 'tableContent';\n columnWidths?: Array<number | null>;\n headerRows?: number;\n rows?: BlockNoteTableRow[];\n}\n\ntype BlockNoteContent = BlockNoteInline[] | BlockNoteTableContent;\n\ninterface MarkdownImportDeps {\n ensureTag: (name: string) => Promise<\n | {\n id: string;\n name: string;\n }\n | {\n tag: {\n id: string;\n name: string;\n };\n }\n >;\n findNotesByTitle: (title: string) => Promise<\n Array<{\n id: string;\n title: string;\n }>\n >;\n}\n\nconst UNSUPPORTED_MARKDOWN_BLOCK_TYPES = new Set(['tableOfContents']);\nconst TAG_PLACEHOLDER_PREFIX = 'OCEAN_BRAIN_TAG_';\nconst TAG_PLACEHOLDER_SUFFIX = '_TOKEN';\n\nconst defaultMarkdownImportDeps: MarkdownImportDeps = {\n ensureTag: async (name) => ensureTagByName(name),\n findNotesByTitle: async (title) => {\n const notes = await models.note.findMany({\n select: {\n id: true,\n title: true,\n },\n where: { title },\n orderBy: { createdAt: 'asc' },\n take: 2,\n });\n\n return notes.map((note) => ({\n id: String(note.id),\n title: note.title,\n }));\n },\n};\n\nfunction stripUnsupportedMarkdownBlocks(blocks: BlockNote[]): BlockNote[] {\n return blocks\n .filter((block) => !UNSUPPORTED_MARKDOWN_BLOCK_TYPES.has(block.type))\n .map((block) => ({\n ...block,\n children: block.children?.length ? stripUnsupportedMarkdownBlocks(block.children) : [],\n }));\n}\n\nfunction createTagPlaceholder(index: number) {\n return `${TAG_PLACEHOLDER_PREFIX}${index}${TAG_PLACEHOLDER_SUFFIX}`;\n}\n\nfunction isTableContent(content: BlockNoteContent | undefined): content is BlockNoteTableContent {\n return !Array.isArray(content) && content?.type === 'tableContent';\n}\n\nfunction mapBlockContent(\n content: BlockNoteContent | undefined,\n mapInline: (inline: BlockNoteInline) => BlockNoteInline,\n): BlockNoteContent | undefined {\n if (Array.isArray(content)) {\n return content.map(mapInline);\n }\n\n if (!isTableContent(content)) {\n return content;\n }\n\n return {\n ...content,\n rows: content.rows?.map((row) => ({\n ...row,\n cells: row.cells?.map((cell) => ({\n ...cell,\n content: cell.content?.map(mapInline),\n })),\n })),\n };\n}\n\nasync function mapBlockContentAsync(\n content: BlockNoteContent | undefined,\n mapInline: (inline: BlockNoteInline) => Promise<BlockNoteInline[]>,\n): Promise<BlockNoteContent | undefined> {\n if (Array.isArray(content)) {\n return (await Promise.all(content.map(mapInline))).flat();\n }\n\n if (!isTableContent(content)) {\n return content;\n }\n\n return {\n ...content,\n rows: await Promise.all(\n (content.rows ?? []).map(async (row) => ({\n ...row,\n cells: await Promise.all(\n (row.cells ?? []).map(async (cell) => ({\n ...cell,\n content: cell.content ? (await Promise.all(cell.content.map(mapInline))).flat() : cell.content,\n })),\n ),\n })),\n ),\n };\n}\n\nfunction visitBlockContent(content: BlockNoteContent | undefined, visitInline: (inline: BlockNoteInline) => void) {\n if (Array.isArray(content)) {\n for (const inline of content) {\n visitInline(inline);\n }\n return;\n }\n\n if (!isTableContent(content)) {\n return;\n }\n\n for (const row of content.rows ?? []) {\n for (const cell of row.cells ?? []) {\n for (const inline of cell.content ?? []) {\n visitInline(inline);\n }\n }\n }\n}\n\nfunction mapBlocks(\n blocks: BlockNote[],\n mapContent: (content: BlockNoteContent | undefined) => BlockNoteContent | undefined,\n): BlockNote[] {\n return blocks.map((block) => ({\n ...block,\n content: mapContent(block.content),\n children: block.children?.length ? mapBlocks(block.children, mapContent) : [],\n }));\n}\n\nasync function mapBlocksAsync(\n blocks: BlockNote[],\n mapContent: (content: BlockNoteContent | undefined) => Promise<BlockNoteContent | undefined>,\n): Promise<BlockNote[]> {\n return Promise.all(\n blocks.map(async (block) => ({\n ...block,\n content: await mapContent(block.content),\n children: block.children?.length ? await mapBlocksAsync(block.children, mapContent) : [],\n })),\n );\n}\n\nfunction preprocessCustomInlineContent(\n blocks: BlockNote[],\n placeholderToTag: Map<string, string>,\n nextPlaceholderIndex: { value: number },\n): BlockNote[] {\n return mapBlocks(blocks, (content) =>\n mapBlockContent(content, (inline) => {\n if (inline.type === 'reference') {\n return {\n type: 'text',\n text: `[[${inline.props?.title || inline.props?.id || ''}]]`,\n styles: {},\n };\n }\n if (inline.type === 'tag') {\n const tag = (inline.props?.tag as string) || '';\n const placeholder = createTagPlaceholder(nextPlaceholderIndex.value++);\n placeholderToTag.set(placeholder, tag);\n return {\n type: 'text',\n text: placeholder,\n styles: {},\n };\n }\n return inline;\n }),\n );\n}\n\nfunction restoreTagPlaceholdersInMarkdown(markdown: string, placeholderToTag: Map<string, string>) {\n let restoredMarkdown = markdown;\n\n for (const [placeholder, tag] of placeholderToTag.entries()) {\n restoredMarkdown = restoredMarkdown.split(placeholder).join(`[${tag}]`);\n }\n\n return restoredMarkdown;\n}\n\nfunction preprocessMarkdownExplicitTags(markdown: string) {\n const placeholderToTag = new Map<string, string>();\n let nextPlaceholderIndex = 0;\n const preprocessedMarkdown = markdown.replace(/\\[([@#][^\\s[\\]]+)\\]/g, (_match, tagToken: string) => {\n const placeholder = createTagPlaceholder(nextPlaceholderIndex++);\n placeholderToTag.set(placeholder, tagToken);\n return placeholder;\n });\n\n return {\n markdown: preprocessedMarkdown,\n placeholderToTag,\n };\n}\n\nfunction findTagPlaceholderAtCursor(text: string, cursor: number, placeholderToTag: Map<string, string>) {\n for (const [placeholder, tagToken] of placeholderToTag.entries()) {\n if (text.startsWith(placeholder, cursor)) {\n return {\n placeholder,\n tagToken,\n };\n }\n }\n\n return null;\n}\n\nfunction restoreRemainingTagPlaceholders(blocks: BlockNote[], placeholderToTag: Map<string, string>): BlockNote[] {\n return mapBlocks(blocks, (content) =>\n mapBlockContent(content, (inline) => {\n if (inline.type !== 'text' || typeof inline.text !== 'string') {\n return inline;\n }\n\n let text = inline.text;\n\n for (const [placeholder, tagToken] of placeholderToTag.entries()) {\n text = text.split(placeholder).join(`[${tagToken}]`);\n }\n\n return {\n ...inline,\n text,\n };\n }),\n );\n}\n\nfunction createTextInline(text: string, styles: Record<string, unknown> = {}): BlockNoteInline {\n return {\n type: 'text',\n text,\n styles,\n };\n}\n\nfunction appendInline(target: BlockNoteInline[], inline: BlockNoteInline) {\n const previous = target.at(-1);\n\n if (\n previous?.type === 'text' &&\n inline.type === 'text' &&\n previous.text !== undefined &&\n inline.text !== undefined &&\n JSON.stringify(previous.styles ?? {}) === JSON.stringify(inline.styles ?? {})\n ) {\n previous.text += inline.text;\n return;\n }\n\n target.push(inline);\n}\n\nasync function restoreCustomInlineContent(\n blocks: BlockNote[],\n deps: MarkdownImportDeps,\n placeholderToTag: Map<string, string>,\n): Promise<BlockNote[]> {\n const tagCache = new Map<string, Promise<{ id: string; tag: string }>>();\n const noteCache = new Map<string, Promise<Array<{ id: string; title: string }>>>();\n\n const getTag = (token: string) => {\n const normalizedToken = token.startsWith('#') ? `@${token.slice(1)}` : token;\n let existing = tagCache.get(normalizedToken);\n\n if (!existing) {\n existing = deps.ensureTag(normalizedToken.slice(1)).then((result) => ({\n id: 'tag' in result ? result.tag.id : result.id,\n tag: 'tag' in result ? result.tag.name : result.name,\n }));\n tagCache.set(normalizedToken, existing);\n }\n\n return existing;\n };\n\n const getNotesByTitle = (title: string) => {\n let existing = noteCache.get(title);\n\n if (!existing) {\n existing = deps.findNotesByTitle(title);\n noteCache.set(title, existing);\n }\n\n return existing;\n };\n\n const restoreTextInline = async (inline: BlockNoteInline): Promise<BlockNoteInline[]> => {\n if (inline.type !== 'text' || typeof inline.text !== 'string' || !inline.text || inline.styles?.code === true) {\n return [inline];\n }\n\n const restored: BlockNoteInline[] = [];\n const styles = inline.styles ?? {};\n let cursor = 0;\n\n while (cursor < inline.text.length) {\n if (inline.text.startsWith('[[', cursor)) {\n const closeIndex = inline.text.indexOf(']]', cursor + 2);\n\n if (closeIndex !== -1) {\n const title = inline.text.slice(cursor + 2, closeIndex);\n const matchingNotes = await getNotesByTitle(title);\n\n if (matchingNotes.length === 1) {\n appendInline(restored, {\n type: 'reference',\n props: {\n id: matchingNotes[0].id,\n title: matchingNotes[0].title,\n },\n });\n cursor = closeIndex + 2;\n continue;\n }\n }\n }\n\n const tagPlaceholderMatch = findTagPlaceholderAtCursor(inline.text, cursor, placeholderToTag);\n\n if (tagPlaceholderMatch) {\n const tag = await getTag(tagPlaceholderMatch.tagToken);\n appendInline(restored, {\n type: 'tag',\n props: tag,\n });\n cursor += tagPlaceholderMatch.placeholder.length;\n continue;\n }\n\n let nextCursor = inline.text.length;\n\n const nextReferenceCursor = inline.text.indexOf('[[', cursor + 1);\n\n if (nextReferenceCursor !== -1) {\n nextCursor = Math.min(nextCursor, nextReferenceCursor);\n }\n\n for (const placeholder of placeholderToTag.keys()) {\n const nextPlaceholderCursor = inline.text.indexOf(placeholder, cursor + 1);\n\n if (nextPlaceholderCursor !== -1) {\n nextCursor = Math.min(nextCursor, nextPlaceholderCursor);\n }\n }\n\n appendInline(restored, createTextInline(inline.text.slice(cursor, nextCursor), styles));\n cursor = nextCursor;\n }\n\n return restored;\n };\n\n return mapBlocksAsync(blocks, (content) => mapBlockContentAsync(content, restoreTextInline));\n}\n\nfunction collectTagIds(blocks: BlockNote[]): string[] {\n const tagIds = new Set<string>();\n\n const visit = (items: BlockNote[]) => {\n for (const block of items) {\n visitBlockContent(block.content, (inline) => {\n if (inline.type === 'tag' && typeof inline.props?.id === 'string') {\n tagIds.add(inline.props.id);\n }\n });\n\n if (block.children?.length) {\n visit(block.children);\n }\n }\n };\n\n visit(blocks);\n return [...tagIds];\n}\n\nlet editorInstance: ServerBlockNoteEditor | null = null;\n\nfunction getEditor(): ServerBlockNoteEditor {\n if (!editorInstance) {\n editorInstance = ServerBlockNoteEditor.create();\n }\n return editorInstance;\n}\n\nexport async function blocksToMarkdown(contentJson: string): Promise<string> {\n try {\n const blocks = JSON.parse(contentJson);\n const supportedBlocks = stripUnsupportedMarkdownBlocks(blocks);\n const placeholderToTag = new Map<string, string>();\n const processed = preprocessCustomInlineContent(supportedBlocks, placeholderToTag, { value: 0 });\n const editor = getEditor();\n const markdown = await editor.blocksToMarkdownLossy(\n processed as Parameters<typeof editor.blocksToMarkdownLossy>[0],\n );\n return restoreTagPlaceholdersInMarkdown(markdown, placeholderToTag);\n } catch {\n return '';\n }\n}\n\nexport async function markdownToBlocksJson(\n markdown: string,\n deps: MarkdownImportDeps = defaultMarkdownImportDeps,\n): Promise<string> {\n const editor = getEditor();\n const preprocessedMarkdown = preprocessMarkdownExplicitTags(markdown);\n const blocks = await editor.tryParseMarkdownToBlocks(preprocessedMarkdown.markdown);\n const restoredBlocks = await restoreCustomInlineContent(\n blocks as BlockNote[],\n deps,\n preprocessedMarkdown.placeholderToTag,\n );\n return JSON.stringify(restoreRemainingTagPlaceholders(restoredBlocks, preprocessedMarkdown.placeholderToTag));\n}\n\nexport function extractTagIdsFromContentJson(contentJson: string): string[] {\n const blocks = JSON.parse(contentJson) as BlockNote[];\n return collectTagIds(blocks);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,75 @@
1
+ import { backfillStaleNoteSearchText } from "../features/note/services/search-backfill.js";
2
+ let activeDataMaintenanceTimer = null;
3
+ const DEFAULT_DATA_MAINTENANCE_INTERVAL_MS = 15 * 60 * 1e3;
4
+ const createDataMaintenanceService = (jobs) => {
5
+ let activeDataMaintenancePromise = null;
6
+ const runNow = async (limit) => {
7
+ const results = [];
8
+ for (const job of jobs) {
9
+ const processedCount = await job.run(limit);
10
+ if (processedCount > 0) {
11
+ results.push({
12
+ key: job.key,
13
+ processedCount
14
+ });
15
+ }
16
+ }
17
+ return results;
18
+ };
19
+ const runInBackground = (limit) => {
20
+ if (activeDataMaintenancePromise) {
21
+ return activeDataMaintenancePromise;
22
+ }
23
+ activeDataMaintenancePromise = runNow(limit).finally(() => {
24
+ activeDataMaintenancePromise = null;
25
+ });
26
+ return activeDataMaintenancePromise;
27
+ };
28
+ return {
29
+ runNow,
30
+ runInBackground
31
+ };
32
+ };
33
+ const defaultDataMaintenanceService = createDataMaintenanceService([
34
+ {
35
+ key: "note-search-projection",
36
+ run: backfillStaleNoteSearchText
37
+ }
38
+ ]);
39
+ const runDataMaintenance = async (limit) => {
40
+ return defaultDataMaintenanceService.runNow(limit);
41
+ };
42
+ const runDataMaintenanceInBackground = (limit) => {
43
+ return defaultDataMaintenanceService.runInBackground(limit);
44
+ };
45
+ const startDataMaintenanceScheduler = (options = {}) => {
46
+ if (activeDataMaintenanceTimer) {
47
+ return () => void 0;
48
+ }
49
+ const intervalMs = options.intervalMs ?? DEFAULT_DATA_MAINTENANCE_INTERVAL_MS;
50
+ const tick = () => {
51
+ const runInBackground = options.runInBackground ?? runDataMaintenanceInBackground;
52
+ void runInBackground().then((results) => {
53
+ options.onResults?.(results);
54
+ }).catch((error) => {
55
+ options.onError?.(error);
56
+ });
57
+ };
58
+ tick();
59
+ activeDataMaintenanceTimer = setInterval(tick, intervalMs);
60
+ activeDataMaintenanceTimer.unref?.();
61
+ return () => {
62
+ if (!activeDataMaintenanceTimer) {
63
+ return;
64
+ }
65
+ clearInterval(activeDataMaintenanceTimer);
66
+ activeDataMaintenanceTimer = null;
67
+ };
68
+ };
69
+ export {
70
+ createDataMaintenanceService,
71
+ runDataMaintenance,
72
+ runDataMaintenanceInBackground,
73
+ startDataMaintenanceScheduler
74
+ };
75
+ //# sourceMappingURL=data-maintenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/modules/data-maintenance.ts"],"sourcesContent":["import { backfillStaleNoteSearchText } from '~/features/note/services/search-backfill.js';\n\nexport interface DataMaintenanceJobResult {\n key: string;\n processedCount: number;\n}\n\ninterface DataMaintenanceSchedulerOptions {\n intervalMs?: number;\n onError?: (error: unknown) => void;\n onResults?: (results: DataMaintenanceJobResult[]) => void;\n runInBackground?: (limit?: number) => Promise<DataMaintenanceJobResult[]>;\n}\n\ninterface DataMaintenanceJob {\n key: string;\n run: (limit?: number) => Promise<number>;\n}\n\ninterface DataMaintenanceService {\n runNow: (limit?: number) => Promise<DataMaintenanceJobResult[]>;\n runInBackground: (limit?: number) => Promise<DataMaintenanceJobResult[]>;\n}\n\nlet activeDataMaintenanceTimer: ReturnType<typeof setInterval> | null = null;\nconst DEFAULT_DATA_MAINTENANCE_INTERVAL_MS = 15 * 60 * 1000;\n\nexport const createDataMaintenanceService = (jobs: DataMaintenanceJob[]): DataMaintenanceService => {\n let activeDataMaintenancePromise: Promise<DataMaintenanceJobResult[]> | null = null;\n\n const runNow = async (limit?: number) => {\n const results: DataMaintenanceJobResult[] = [];\n\n for (const job of jobs) {\n const processedCount = await job.run(limit);\n\n if (processedCount > 0) {\n results.push({\n key: job.key,\n processedCount,\n });\n }\n }\n\n return results;\n };\n\n const runInBackground = (limit?: number) => {\n if (activeDataMaintenancePromise) {\n return activeDataMaintenancePromise;\n }\n\n activeDataMaintenancePromise = runNow(limit).finally(() => {\n activeDataMaintenancePromise = null;\n });\n\n return activeDataMaintenancePromise;\n };\n\n return {\n runNow,\n runInBackground,\n };\n};\n\nconst defaultDataMaintenanceService = createDataMaintenanceService([\n {\n key: 'note-search-projection',\n run: backfillStaleNoteSearchText,\n },\n]);\n\nexport const runDataMaintenance = async (limit?: number) => {\n return defaultDataMaintenanceService.runNow(limit);\n};\n\nexport const runDataMaintenanceInBackground = (limit?: number) => {\n return defaultDataMaintenanceService.runInBackground(limit);\n};\n\nexport const startDataMaintenanceScheduler = (options: DataMaintenanceSchedulerOptions = {}) => {\n if (activeDataMaintenanceTimer) {\n return () => undefined;\n }\n\n const intervalMs = options.intervalMs ?? DEFAULT_DATA_MAINTENANCE_INTERVAL_MS;\n\n const tick = () => {\n const runInBackground = options.runInBackground ?? runDataMaintenanceInBackground;\n\n void runInBackground()\n .then((results) => {\n options.onResults?.(results);\n })\n .catch((error) => {\n options.onError?.(error);\n });\n };\n\n tick();\n\n activeDataMaintenanceTimer = setInterval(tick, intervalMs);\n activeDataMaintenanceTimer.unref?.();\n\n return () => {\n if (!activeDataMaintenanceTimer) {\n return;\n }\n\n clearInterval(activeDataMaintenanceTimer);\n activeDataMaintenanceTimer = null;\n };\n};\n"],"mappings":"AAAA,SAAS,mCAAmC;AAwB5C,IAAI,6BAAoE;AACxE,MAAM,uCAAuC,KAAK,KAAK;AAEhD,MAAM,+BAA+B,CAAC,SAAuD;AAChG,MAAI,+BAA2E;AAE/E,QAAM,SAAS,OAAO,UAAmB;AACrC,UAAM,UAAsC,CAAC;AAE7C,eAAW,OAAO,MAAM;AACpB,YAAM,iBAAiB,MAAM,IAAI,IAAI,KAAK;AAE1C,UAAI,iBAAiB,GAAG;AACpB,gBAAQ,KAAK;AAAA,UACT,KAAK,IAAI;AAAA,UACT;AAAA,QACJ,CAAC;AAAA,MACL;AAAA,IACJ;AAEA,WAAO;AAAA,EACX;AAEA,QAAM,kBAAkB,CAAC,UAAmB;AACxC,QAAI,8BAA8B;AAC9B,aAAO;AAAA,IACX;AAEA,mCAA+B,OAAO,KAAK,EAAE,QAAQ,MAAM;AACvD,qCAA+B;AAAA,IACnC,CAAC;AAED,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;AAEA,MAAM,gCAAgC,6BAA6B;AAAA,EAC/D;AAAA,IACI,KAAK;AAAA,IACL,KAAK;AAAA,EACT;AACJ,CAAC;AAEM,MAAM,qBAAqB,OAAO,UAAmB;AACxD,SAAO,8BAA8B,OAAO,KAAK;AACrD;AAEO,MAAM,iCAAiC,CAAC,UAAmB;AAC9D,SAAO,8BAA8B,gBAAgB,KAAK;AAC9D;AAEO,MAAM,gCAAgC,CAAC,UAA2C,CAAC,MAAM;AAC5F,MAAI,4BAA4B;AAC5B,WAAO,MAAM;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,cAAc;AAEzC,QAAM,OAAO,MAAM;AACf,UAAM,kBAAkB,QAAQ,mBAAmB;AAEnD,SAAK,gBAAgB,EAChB,KAAK,CAAC,YAAY;AACf,cAAQ,YAAY,OAAO;AAAA,IAC/B,CAAC,EACA,MAAM,CAAC,UAAU;AACd,cAAQ,UAAU,KAAK;AAAA,IAC3B,CAAC;AAAA,EACT;AAEA,OAAK;AAEL,+BAA6B,YAAY,MAAM,UAAU;AACzD,6BAA2B,QAAQ;AAEnC,SAAO,MAAM;AACT,QAAI,CAAC,4BAA4B;AAC7B;AAAA,IACJ;AAEA,kBAAc,0BAA0B;AACxC,iCAA6B;AAAA,EACjC;AACJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/error-handler.ts"],"sourcesContent":["import type { ErrorRequestHandler } from 'express';\n\nexport class AppError extends Error {\n code: string;\n status: number;\n\n constructor(status: number, code: string, message: string) {\n super(message);\n this.name = 'AppError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport const createAppError = (status: number, code: string, message: string) => {\n return new AppError(status, code, message);\n};\n\nexport const createErrorHandler = (): ErrorRequestHandler => {\n return (error, _req, res, next) => {\n if (res.headersSent) {\n next(error);\n return;\n }\n\n if (error instanceof AppError) {\n res.status(error.status).json({\n code: error.code,\n message: error.message\n }).end();\n return;\n }\n\n const message = error instanceof Error ? error.stack || error.message : String(error);\n process.stderr.write(`[error] ${message}\\n`);\n\n res.status(500).json({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Internal Server Error'\n }).end();\n };\n};\n"],"mappings":"AAEO,MAAM,iBAAiB,MAAM;AAAA,EAChC;AAAA,EACA;AAAA,EAEA,YAAY,QAAgB,MAAc,SAAiB;AACvD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EAChB;AACJ;AAEO,MAAM,iBAAiB,CAAC,QAAgB,MAAc,YAAoB;AAC7E,SAAO,IAAI,SAAS,QAAQ,MAAM,OAAO;AAC7C;AAEO,MAAM,qBAAqB,MAA2B;AACzD,SAAO,CAAC,OAAO,MAAM,KAAK,SAAS;AAC/B,QAAI,IAAI,aAAa;AACjB,WAAK,KAAK;AACV;AAAA,IACJ;AAEA,QAAI,iBAAiB,UAAU;AAC3B,UAAI,OAAO,MAAM,MAAM,EAAE,KAAK;AAAA,QAC1B,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,MACnB,CAAC,EAAE,IAAI;AACP;AAAA,IACJ;AAEA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,KAAK;AACpF,YAAQ,OAAO,MAAM,WAAW,OAAO;AAAA,CAAI;AAE3C,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC,EAAE,IAAI;AAAA,EACX;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/error-handler.ts"],"sourcesContent":["import type { ErrorRequestHandler } from 'express';\n\nexport class AppError extends Error {\n code: string;\n status: number;\n\n constructor(status: number, code: string, message: string) {\n super(message);\n this.name = 'AppError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport const createAppError = (status: number, code: string, message: string) => {\n return new AppError(status, code, message);\n};\n\nexport const createErrorHandler = (): ErrorRequestHandler => {\n return (error, _req, res, next) => {\n if (res.headersSent) {\n next(error);\n return;\n }\n\n if (error instanceof AppError) {\n res.status(error.status)\n .json({\n code: error.code,\n message: error.message,\n })\n .end();\n return;\n }\n\n const message = error instanceof Error ? error.stack || error.message : String(error);\n process.stderr.write(`[error] ${message}\\n`);\n\n res.status(500)\n .json({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Internal Server Error',\n })\n .end();\n };\n};\n"],"mappings":"AAEO,MAAM,iBAAiB,MAAM;AAAA,EAChC;AAAA,EACA;AAAA,EAEA,YAAY,QAAgB,MAAc,SAAiB;AACvD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EAChB;AACJ;AAEO,MAAM,iBAAiB,CAAC,QAAgB,MAAc,YAAoB;AAC7E,SAAO,IAAI,SAAS,QAAQ,MAAM,OAAO;AAC7C;AAEO,MAAM,qBAAqB,MAA2B;AACzD,SAAO,CAAC,OAAO,MAAM,KAAK,SAAS;AAC/B,QAAI,IAAI,aAAa;AACjB,WAAK,KAAK;AACV;AAAA,IACJ;AAEA,QAAI,iBAAiB,UAAU;AAC3B,UAAI,OAAO,MAAM,MAAM,EAClB,KAAK;AAAA,QACF,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,MACnB,CAAC,EACA,IAAI;AACT;AAAA,IACJ;AAEA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,KAAK;AACpF,YAAQ,OAAO,MAAM,WAAW,OAAO;AAAA,CAAI;AAE3C,QAAI,OAAO,GAAG,EACT,KAAK;AAAA,MACF,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC,EACA,IAAI;AAAA,EACb;AACJ;","names":[]}
@@ -1,36 +1,18 @@
1
1
  import expressWinston from "express-winston";
2
2
  import winston from "winston";
3
3
  const loggerOptions = {
4
- transports: [
5
- new winston.transports.Console()
6
- ],
4
+ transports: [new winston.transports.Console()],
7
5
  meta: true,
8
- requestWhitelist: [
9
- "httpVersion",
10
- "method",
11
- "originalUrl",
12
- "url",
13
- "query",
14
- "params",
15
- "ip",
16
- "headers"
17
- ],
18
- responseWhitelist: [
19
- "statusCode"
20
- ],
21
- headerBlacklist: [
22
- "authorization",
23
- "cookie",
24
- "set-cookie",
25
- "x-api-key"
26
- ],
6
+ requestWhitelist: ["httpVersion", "method", "originalUrl", "url", "query", "params", "ip", "headers"],
7
+ responseWhitelist: ["statusCode"],
8
+ headerBlacklist: ["authorization", "cookie", "set-cookie", "x-api-key"],
27
9
  requestFilter: (req, propName) => {
28
10
  if (propName === "headers") {
29
11
  const h = req.headers || {};
30
12
  return {
31
13
  "user-agent": h["user-agent"],
32
14
  "content-type": h["content-type"],
33
- "accept": h["accept"],
15
+ accept: h["accept"],
34
16
  "x-forwarded-for": h["x-forwarded-for"],
35
17
  "x-real-ip": h["x-real-ip"]
36
18
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/logger.ts"],"sourcesContent":["import type { LoggerOptions } from 'express-winston';\nimport expressWinston from 'express-winston';\nimport winston from 'winston';\nimport type { TransformableInfo } from 'logform';\n\nexport const loggerOptions: LoggerOptions = {\n transports: [\n new winston.transports.Console()\n ],\n meta: true,\n requestWhitelist: [\n 'httpVersion',\n 'method',\n 'originalUrl',\n 'url',\n 'query',\n 'params',\n 'ip',\n 'headers'\n ],\n responseWhitelist: [\n 'statusCode'\n ],\n headerBlacklist: [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key'\n ],\n requestFilter: (\n req: { headers?: Record<string, string | string[] | undefined> } & Record<string, unknown>,\n propName: string\n ): unknown => {\n if (propName === 'headers') {\n const h = req.headers || {};\n return {\n 'user-agent': h['user-agent'],\n 'content-type': h['content-type'],\n 'accept': h['accept'],\n 'x-forwarded-for': h['x-forwarded-for'],\n 'x-real-ip': h['x-real-ip']\n };\n }\n return (req as Record<string, unknown>)[propName];\n },\n responseFilter: (res: Record<string, unknown>, propName: string): unknown => {\n return res[propName];\n },\n format: winston.format.combine(\n winston.format.colorize({ all: true }),\n winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n winston.format.printf((info: TransformableInfo) => {\n const meta = (info as unknown as { meta?: Record<string, unknown> }).meta || {};\n const req = (meta.req as Record<string, unknown>) || {};\n const res = (meta.res as Record<string, unknown>) || {};\n const rt = (meta as Record<string, unknown>).responseTime as number | undefined;\n const method = req.method as string | undefined;\n const url = (req.originalUrl as string | undefined) || (req.url as string | undefined);\n const status = res.statusCode as number | undefined;\n const headers = (req.headers as Record<string, string> | undefined) || {};\n const ua = headers['user-agent'];\n const fwd = headers['x-forwarded-for'];\n const fwdFirst = typeof fwd === 'string' ? fwd.split(',')[0].trim() : undefined;\n const realIp = fwdFirst || headers['x-real-ip'];\n const socketIp = (req as unknown as { ip?: string; socket?: { remoteAddress?: string } }).ip\n || (req as unknown as { socket?: { remoteAddress?: string } }).socket?.remoteAddress;\n const clientIp = fwdFirst || realIp || socketIp || '';\n return `${info.timestamp as string} ${info.level as string}: [${status ?? ''}] ${method || ''} ${url || ''} ${rt ?? ''}ms ip=\"${clientIp}\" ua=\"${ua || ''}\"`;\n })\n ),\n colorize: true,\n expressFormat: false\n};\n\nexport default expressWinston.logger(loggerOptions);\n"],"mappings":"AACA,OAAO,oBAAoB;AAC3B,OAAO,aAAa;AAGb,MAAM,gBAA+B;AAAA,EACxC,YAAY;AAAA,IACR,IAAI,QAAQ,WAAW,QAAQ;AAAA,EACnC;AAAA,EACA,MAAM;AAAA,EACN,kBAAkB;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAAA,EACA,mBAAmB;AAAA,IACf;AAAA,EACJ;AAAA,EACA,iBAAiB;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAAA,EACA,eAAe,CACX,KACA,aACU;AACV,QAAI,aAAa,WAAW;AACxB,YAAM,IAAI,IAAI,WAAW,CAAC;AAC1B,aAAO;AAAA,QACH,cAAc,EAAE,YAAY;AAAA,QAC5B,gBAAgB,EAAE,cAAc;AAAA,QAChC,UAAU,EAAE,QAAQ;AAAA,QACpB,mBAAmB,EAAE,iBAAiB;AAAA,QACtC,aAAa,EAAE,WAAW;AAAA,MAC9B;AAAA,IACJ;AACA,WAAQ,IAAgC,QAAQ;AAAA,EACpD;AAAA,EACA,gBAAgB,CAAC,KAA8B,aAA8B;AACzE,WAAO,IAAI,QAAQ;AAAA,EACvB;AAAA,EACA,QAAQ,QAAQ,OAAO;AAAA,IACnB,QAAQ,OAAO,SAAS,EAAE,KAAK,KAAK,CAAC;AAAA,IACrC,QAAQ,OAAO,UAAU,EAAE,QAAQ,sBAAsB,CAAC;AAAA,IAC1D,QAAQ,OAAO,OAAO,CAAC,SAA4B;AAC/C,YAAM,OAAQ,KAAuD,QAAQ,CAAC;AAC9E,YAAM,MAAO,KAAK,OAAmC,CAAC;AACtD,YAAM,MAAO,KAAK,OAAmC,CAAC;AACtD,YAAM,KAAM,KAAiC;AAC7C,YAAM,SAAS,IAAI;AACnB,YAAM,MAAO,IAAI,eAAuC,IAAI;AAC5D,YAAM,SAAS,IAAI;AACnB,YAAM,UAAW,IAAI,WAAkD,CAAC;AACxE,YAAM,KAAK,QAAQ,YAAY;AAC/B,YAAM,MAAM,QAAQ,iBAAiB;AACrC,YAAM,WAAW,OAAO,QAAQ,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,IAAI;AACtE,YAAM,SAAS,YAAY,QAAQ,WAAW;AAC9C,YAAM,WAAY,IAAwE,MAClF,IAA2D,QAAQ;AAC3E,YAAM,WAAW,YAAY,UAAU,YAAY;AACnD,aAAO,GAAG,KAAK,SAAmB,IAAI,KAAK,KAAe,MAAM,UAAU,EAAE,KAAK,UAAU,EAAE,IAAI,OAAO,EAAE,IAAI,MAAM,EAAE,UAAU,QAAQ,SAAS,MAAM,EAAE;AAAA,IAC7J,CAAC;AAAA,EACL;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AACnB;AAEA,IAAO,iBAAQ,eAAe,OAAO,aAAa;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/logger.ts"],"sourcesContent":["import type { LoggerOptions } from 'express-winston';\nimport expressWinston from 'express-winston';\nimport type { TransformableInfo } from 'logform';\nimport winston from 'winston';\n\nexport const loggerOptions: LoggerOptions = {\n transports: [new winston.transports.Console()],\n meta: true,\n requestWhitelist: ['httpVersion', 'method', 'originalUrl', 'url', 'query', 'params', 'ip', 'headers'],\n responseWhitelist: ['statusCode'],\n headerBlacklist: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n requestFilter: (\n req: { headers?: Record<string, string | string[] | undefined> } & Record<string, unknown>,\n propName: string,\n ): unknown => {\n if (propName === 'headers') {\n const h = req.headers || {};\n return {\n 'user-agent': h['user-agent'],\n 'content-type': h['content-type'],\n accept: h['accept'],\n 'x-forwarded-for': h['x-forwarded-for'],\n 'x-real-ip': h['x-real-ip'],\n };\n }\n return (req as Record<string, unknown>)[propName];\n },\n responseFilter: (res: Record<string, unknown>, propName: string): unknown => {\n return res[propName];\n },\n format: winston.format.combine(\n winston.format.colorize({ all: true }),\n winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n winston.format.printf((info: TransformableInfo) => {\n const meta = (info as unknown as { meta?: Record<string, unknown> }).meta || {};\n const req = (meta.req as Record<string, unknown>) || {};\n const res = (meta.res as Record<string, unknown>) || {};\n const rt = (meta as Record<string, unknown>).responseTime as number | undefined;\n const method = req.method as string | undefined;\n const url = (req.originalUrl as string | undefined) || (req.url as string | undefined);\n const status = res.statusCode as number | undefined;\n const headers = (req.headers as Record<string, string> | undefined) || {};\n const ua = headers['user-agent'];\n const fwd = headers['x-forwarded-for'];\n const fwdFirst = typeof fwd === 'string' ? fwd.split(',')[0].trim() : undefined;\n const realIp = fwdFirst || headers['x-real-ip'];\n const socketIp =\n (req as unknown as { ip?: string; socket?: { remoteAddress?: string } }).ip ||\n (req as unknown as { socket?: { remoteAddress?: string } }).socket?.remoteAddress;\n const clientIp = fwdFirst || realIp || socketIp || '';\n return `${info.timestamp as string} ${info.level as string}: [${status ?? ''}] ${method || ''} ${url || ''} ${rt ?? ''}ms ip=\"${clientIp}\" ua=\"${ua || ''}\"`;\n }),\n ),\n colorize: true,\n expressFormat: false,\n};\n\nexport default expressWinston.logger(loggerOptions);\n"],"mappings":"AACA,OAAO,oBAAoB;AAE3B,OAAO,aAAa;AAEb,MAAM,gBAA+B;AAAA,EACxC,YAAY,CAAC,IAAI,QAAQ,WAAW,QAAQ,CAAC;AAAA,EAC7C,MAAM;AAAA,EACN,kBAAkB,CAAC,eAAe,UAAU,eAAe,OAAO,SAAS,UAAU,MAAM,SAAS;AAAA,EACpG,mBAAmB,CAAC,YAAY;AAAA,EAChC,iBAAiB,CAAC,iBAAiB,UAAU,cAAc,WAAW;AAAA,EACtE,eAAe,CACX,KACA,aACU;AACV,QAAI,aAAa,WAAW;AACxB,YAAM,IAAI,IAAI,WAAW,CAAC;AAC1B,aAAO;AAAA,QACH,cAAc,EAAE,YAAY;AAAA,QAC5B,gBAAgB,EAAE,cAAc;AAAA,QAChC,QAAQ,EAAE,QAAQ;AAAA,QAClB,mBAAmB,EAAE,iBAAiB;AAAA,QACtC,aAAa,EAAE,WAAW;AAAA,MAC9B;AAAA,IACJ;AACA,WAAQ,IAAgC,QAAQ;AAAA,EACpD;AAAA,EACA,gBAAgB,CAAC,KAA8B,aAA8B;AACzE,WAAO,IAAI,QAAQ;AAAA,EACvB;AAAA,EACA,QAAQ,QAAQ,OAAO;AAAA,IACnB,QAAQ,OAAO,SAAS,EAAE,KAAK,KAAK,CAAC;AAAA,IACrC,QAAQ,OAAO,UAAU,EAAE,QAAQ,sBAAsB,CAAC;AAAA,IAC1D,QAAQ,OAAO,OAAO,CAAC,SAA4B;AAC/C,YAAM,OAAQ,KAAuD,QAAQ,CAAC;AAC9E,YAAM,MAAO,KAAK,OAAmC,CAAC;AACtD,YAAM,MAAO,KAAK,OAAmC,CAAC;AACtD,YAAM,KAAM,KAAiC;AAC7C,YAAM,SAAS,IAAI;AACnB,YAAM,MAAO,IAAI,eAAuC,IAAI;AAC5D,YAAM,SAAS,IAAI;AACnB,YAAM,UAAW,IAAI,WAAkD,CAAC;AACxE,YAAM,KAAK,QAAQ,YAAY;AAC/B,YAAM,MAAM,QAAQ,iBAAiB;AACrC,YAAM,WAAW,OAAO,QAAQ,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,IAAI;AACtE,YAAM,SAAS,YAAY,QAAQ,WAAW;AAC9C,YAAM,WACD,IAAwE,MACxE,IAA2D,QAAQ;AACxE,YAAM,WAAW,YAAY,UAAU,YAAY;AACnD,aAAO,GAAG,KAAK,SAAmB,IAAI,KAAK,KAAe,MAAM,UAAU,EAAE,KAAK,UAAU,EAAE,IAAI,OAAO,EAAE,IAAI,MAAM,EAAE,UAAU,QAAQ,SAAS,MAAM,EAAE;AAAA,IAC7J,CAAC;AAAA,EACL;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AACnB;AAEA,IAAO,iBAAQ,eAAe,OAAO,aAAa;","names":[]}
@@ -53,13 +53,12 @@ const createReadOnlyMcpValidationRule = () => {
53
53
  if (node.operation === "query") {
54
54
  return;
55
55
  }
56
- context.reportError(new GraphQLError(
57
- "MCP endpoint is read-only",
58
- {
56
+ context.reportError(
57
+ new GraphQLError("MCP endpoint is read-only", {
59
58
  nodes: [node],
60
59
  extensions: { code: "FORBIDDEN" }
61
- }
62
- ));
60
+ })
61
+ );
63
62
  }
64
63
  };
65
64
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, Response, RequestHandler } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (\n _authConfig: AuthConfig,\n mcpAdminAuth: McpAdminAuthPort\n): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res\n .status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.'\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res\n .status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.'\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res\n .status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.'\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res\n .status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.'\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(new GraphQLError(\n 'MCP endpoint is read-only',\n {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' }\n }\n ));\n }\n };\n };\n};\n"],"mappings":"AAEA,SAAS,oBAAoB;AAc7B,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AAE1D,MAAM,kBAAkB,CAAC,wBAAiC;AACtD,MAAI,CAAC,qBAAqB,WAAW,SAAS,GAAG;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,oBAAoB,MAAM,UAAU,MAAM,EAAE,KAAK,KAAK;AACjE;AAEO,MAAM,0BAA0B,CACnC,aACA,iBACiB;AACjB,SAAO,OAAO,KAAc,KAAe,SAAuB;AAC9D,QAAI;AACA,YAAM,SAAS,MAAM,aAAa,UAAU;AAC5C,UAAI,CAAC,OAAO,SAAS;AACjB,YACK,OAAO,GAAG,EACV,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,cAAc,gBAAgB,IAAI,QAAQ,aAAa;AAE7D,UAAI,CAAC,aAAa;AACd,YACK,OAAO,GAAG,EACV,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,aAAa,MAAM,aAAa,uBAAuB,WAAW;AAExE,UAAI,CAAC,WAAW,MAAM,WAAW,WAAW,kBAAkB;AAC1D,YACK,OAAO,GAAG,EACV,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,UAAI,CAAC,WAAW,IAAI;AAChB,YACK,OAAO,GAAG,EACV,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,WAAK;AAAA,IACT,SAAS,OAAO;AACZ,WAAK,KAAK;AAAA,IACd;AAAA,EACJ;AACJ;AAEO,MAAM,kCAAkC,MAAsB;AACjE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,SAAS;AAC5B;AAAA,QACJ;AAEA,gBAAQ,YAAY,IAAI;AAAA,UACpB;AAAA,UACA;AAAA,YACI,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY,EAAE,MAAM,YAAY;AAAA,UACpC;AAAA,QACJ,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AAEA,SAAS,oBAAoB;AAc7B,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AAE1D,MAAM,kBAAkB,CAAC,wBAAiC;AACtD,MAAI,CAAC,qBAAqB,WAAW,SAAS,GAAG;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,oBAAoB,MAAM,UAAU,MAAM,EAAE,KAAK,KAAK;AACjE;AAEO,MAAM,0BAA0B,CAAC,aAAyB,iBAAmD;AAChH,SAAO,OAAO,KAAc,KAAe,SAAuB;AAC9D,QAAI;AACA,YAAM,SAAS,MAAM,aAAa,UAAU;AAC5C,UAAI,CAAC,OAAO,SAAS;AACjB,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,cAAc,gBAAgB,IAAI,QAAQ,aAAa;AAE7D,UAAI,CAAC,aAAa;AACd,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,aAAa,MAAM,aAAa,uBAAuB,WAAW;AAExE,UAAI,CAAC,WAAW,MAAM,WAAW,WAAW,kBAAkB;AAC1D,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,UAAI,CAAC,WAAW,IAAI;AAChB,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,WAAK;AAAA,IACT,SAAS,OAAO;AACZ,WAAK,KAAK;AAAA,IACd;AAAA,EACJ;AACJ;AAEO,MAAM,kCAAkC,MAAsB;AACjE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,SAAS;AAC5B;AAAA,QACJ;AAEA,gBAAQ;AAAA,UACJ,IAAI,aAAa,6BAA6B;AAAA,YAC1C,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY,EAAE,MAAM,YAAY;AAAA,UACpC,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/mcp-token.ts"],"sourcesContent":["import crypto from 'crypto';\n\nconst TOKEN_BYTE_LENGTH = 32;\nconst HASH_ALGORITHM = 'sha256';\n\nexport interface IssuedMcpToken {\n plaintext: string;\n hash: string;\n}\n\nconst toTokenHash = (token: string) => {\n return crypto\n .createHash(HASH_ALGORITHM)\n .update(token, 'utf8')\n .digest('hex');\n};\n\nexport const issueMcpToken = (): IssuedMcpToken => {\n const plaintext = crypto.randomBytes(TOKEN_BYTE_LENGTH).toString('base64url');\n const hash = toTokenHash(plaintext);\n\n return {\n plaintext,\n hash\n };\n};\n\nexport const verifyMcpToken = (storedHash: string, presentedToken: string) => {\n const presentedHash = toTokenHash(presentedToken);\n const storedBuffer = Buffer.from(storedHash, 'utf8');\n const presentedBuffer = Buffer.from(presentedHash, 'utf8');\n\n if (storedBuffer.length !== presentedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(storedBuffer, presentedBuffer);\n};\n"],"mappings":"AAAA,OAAO,YAAY;AAEnB,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AAOvB,MAAM,cAAc,CAAC,UAAkB;AACnC,SAAO,OACF,WAAW,cAAc,EACzB,OAAO,OAAO,MAAM,EACpB,OAAO,KAAK;AACrB;AAEO,MAAM,gBAAgB,MAAsB;AAC/C,QAAM,YAAY,OAAO,YAAY,iBAAiB,EAAE,SAAS,WAAW;AAC5E,QAAM,OAAO,YAAY,SAAS;AAElC,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;AAEO,MAAM,iBAAiB,CAAC,YAAoB,mBAA2B;AAC1E,QAAM,gBAAgB,YAAY,cAAc;AAChD,QAAM,eAAe,OAAO,KAAK,YAAY,MAAM;AACnD,QAAM,kBAAkB,OAAO,KAAK,eAAe,MAAM;AAEzD,MAAI,aAAa,WAAW,gBAAgB,QAAQ;AAChD,WAAO;AAAA,EACX;AAEA,SAAO,OAAO,gBAAgB,cAAc,eAAe;AAC/D;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/mcp-token.ts"],"sourcesContent":["import crypto from 'crypto';\n\nconst TOKEN_BYTE_LENGTH = 32;\nconst HASH_ALGORITHM = 'sha256';\n\nexport interface IssuedMcpToken {\n plaintext: string;\n hash: string;\n}\n\nconst toTokenHash = (token: string) => {\n return crypto.createHash(HASH_ALGORITHM).update(token, 'utf8').digest('hex');\n};\n\nexport const issueMcpToken = (): IssuedMcpToken => {\n const plaintext = crypto.randomBytes(TOKEN_BYTE_LENGTH).toString('base64url');\n const hash = toTokenHash(plaintext);\n\n return {\n plaintext,\n hash,\n };\n};\n\nexport const verifyMcpToken = (storedHash: string, presentedToken: string) => {\n const presentedHash = toTokenHash(presentedToken);\n const storedBuffer = Buffer.from(storedHash, 'utf8');\n const presentedBuffer = Buffer.from(presentedHash, 'utf8');\n\n if (storedBuffer.length !== presentedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(storedBuffer, presentedBuffer);\n};\n"],"mappings":"AAAA,OAAO,YAAY;AAEnB,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AAOvB,MAAM,cAAc,CAAC,UAAkB;AACnC,SAAO,OAAO,WAAW,cAAc,EAAE,OAAO,OAAO,MAAM,EAAE,OAAO,KAAK;AAC/E;AAEO,MAAM,gBAAgB,MAAsB;AAC/C,QAAM,YAAY,OAAO,YAAY,iBAAiB,EAAE,SAAS,WAAW;AAC5E,QAAM,OAAO,YAAY,SAAS;AAElC,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;AAEO,MAAM,iBAAiB,CAAC,YAAoB,mBAA2B;AAC1E,QAAM,gBAAgB,YAAY,cAAc;AAChD,QAAM,eAAe,OAAO,KAAK,YAAY,MAAM;AACnD,QAAM,kBAAkB,OAAO,KAAK,eAAe,MAAM;AAEzD,MAAI,aAAa,WAAW,gBAAgB,QAAQ;AAChD,WAAO;AAAA,EACX;AAEA,SAAO,OAAO,gBAAgB,cAAc,eAAe;AAC/D;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from "child_process";
2
- import path from "path";
3
2
  import { createRequire } from "module";
3
+ import path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  const resolveServerRoot = () => {
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -17,12 +17,7 @@ const createPrismaRuntime = (deps) => ({
17
17
  runMigrateDeploy: () => {
18
18
  deps.execFileSync(
19
19
  process.execPath,
20
- [
21
- deps.resolvePrismaCliEntry(),
22
- "migrate",
23
- "deploy",
24
- `--schema=${resolvePrismaSchemaPath()}`
25
- ],
20
+ [deps.resolvePrismaCliEntry(), "migrate", "deploy", `--schema=${resolvePrismaSchemaPath()}`],
26
21
  {
27
22
  stdio: "inherit",
28
23
  env: { ...process.env }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/prisma-runtime.ts"],"sourcesContent":["import { execFileSync } from 'child_process';\nimport path from 'path';\nimport { createRequire } from 'module';\nimport { fileURLToPath } from 'url';\n\ninterface PrismaRuntimeDeps {\n execFileSync: (\n file: string,\n args: string[],\n options: {\n env: NodeJS.ProcessEnv;\n stdio: 'inherit';\n }\n ) => void;\n resolvePrismaCliEntry: () => string;\n}\n\nconst resolveServerRoot = () => {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n return process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '../..');\n};\n\nexport const resolvePrismaSchemaPath = () => {\n return path.resolve(resolveServerRoot(), 'prisma/schema.prisma');\n};\n\nexport const resolvePrismaCliEntry = () => {\n const require = createRequire(import.meta.url);\n return require.resolve('prisma/build/index.js');\n};\n\nexport const createPrismaRuntime = (deps: PrismaRuntimeDeps) => ({\n runMigrateDeploy: () => {\n deps.execFileSync(\n process.execPath,\n [\n deps.resolvePrismaCliEntry(),\n 'migrate',\n 'deploy',\n `--schema=${resolvePrismaSchemaPath()}`\n ],\n {\n stdio: 'inherit',\n env: { ...process.env }\n }\n );\n }\n});\n\nconst defaultPrismaRuntime = createPrismaRuntime({\n execFileSync,\n resolvePrismaCliEntry\n});\n\nexport const runPrismaMigrateDeploy = () => {\n defaultPrismaRuntime.runMigrateDeploy();\n};\n"],"mappings":"AAAA,SAAS,oBAAoB;AAC7B,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAc9B,MAAM,oBAAoB,MAAM;AAC5B,QAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,SAAO,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,OAAO;AAClF;AAEO,MAAM,0BAA0B,MAAM;AACzC,SAAO,KAAK,QAAQ,kBAAkB,GAAG,sBAAsB;AACnE;AAEO,MAAM,wBAAwB,MAAM;AACvC,QAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,SAAOA,SAAQ,QAAQ,uBAAuB;AAClD;AAEO,MAAM,sBAAsB,CAAC,UAA6B;AAAA,EAC7D,kBAAkB,MAAM;AACpB,SAAK;AAAA,MACD,QAAQ;AAAA,MACR;AAAA,QACI,KAAK,sBAAsB;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,YAAY,wBAAwB,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,QACI,OAAO;AAAA,QACP,KAAK,EAAE,GAAG,QAAQ,IAAI;AAAA,MAC1B;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,MAAM,uBAAuB,oBAAoB;AAAA,EAC7C;AAAA,EACA;AACJ,CAAC;AAEM,MAAM,yBAAyB,MAAM;AACxC,uBAAqB,iBAAiB;AAC1C;","names":["require"]}
1
+ {"version":3,"sources":["../../src/modules/prisma-runtime.ts"],"sourcesContent":["import { execFileSync } from 'child_process';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\ninterface PrismaRuntimeDeps {\n execFileSync: (\n file: string,\n args: string[],\n options: {\n env: NodeJS.ProcessEnv;\n stdio: 'inherit';\n },\n ) => void;\n resolvePrismaCliEntry: () => string;\n}\n\nconst resolveServerRoot = () => {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n return process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '../..');\n};\n\nexport const resolvePrismaSchemaPath = () => {\n return path.resolve(resolveServerRoot(), 'prisma/schema.prisma');\n};\n\nexport const resolvePrismaCliEntry = () => {\n const require = createRequire(import.meta.url);\n return require.resolve('prisma/build/index.js');\n};\n\nexport const createPrismaRuntime = (deps: PrismaRuntimeDeps) => ({\n runMigrateDeploy: () => {\n deps.execFileSync(\n process.execPath,\n [deps.resolvePrismaCliEntry(), 'migrate', 'deploy', `--schema=${resolvePrismaSchemaPath()}`],\n {\n stdio: 'inherit',\n env: { ...process.env },\n },\n );\n },\n});\n\nconst defaultPrismaRuntime = createPrismaRuntime({\n execFileSync,\n resolvePrismaCliEntry,\n});\n\nexport const runPrismaMigrateDeploy = () => {\n defaultPrismaRuntime.runMigrateDeploy();\n};\n"],"mappings":"AAAA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAc9B,MAAM,oBAAoB,MAAM;AAC5B,QAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,SAAO,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,OAAO;AAClF;AAEO,MAAM,0BAA0B,MAAM;AACzC,SAAO,KAAK,QAAQ,kBAAkB,GAAG,sBAAsB;AACnE;AAEO,MAAM,wBAAwB,MAAM;AACvC,QAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,SAAOA,SAAQ,QAAQ,uBAAuB;AAClD;AAEO,MAAM,sBAAsB,CAAC,UAA6B;AAAA,EAC7D,kBAAkB,MAAM;AACpB,SAAK;AAAA,MACD,QAAQ;AAAA,MACR,CAAC,KAAK,sBAAsB,GAAG,WAAW,UAAU,YAAY,wBAAwB,CAAC,EAAE;AAAA,MAC3F;AAAA,QACI,OAAO;AAAA,QACP,KAAK,EAAE,GAAG,QAAQ,IAAI;AAAA,MAC1B;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,MAAM,uBAAuB,oBAAoB;AAAA,EAC7C;AAAA,EACA;AACJ,CAAC;AAEM,MAAM,yBAAyB,MAAM;AACxC,uBAAqB,iBAAiB;AAC1C;","names":["require"]}
@@ -1,5 +1,5 @@
1
1
  const NOTE_SNAPSHOT_RETENTION_DAYS = 14;
2
- const NOTE_SNAPSHOT_MAX_PER_NOTE = 10;
2
+ const NOTE_SNAPSHOT_MAX_PER_NOTE = 5;
3
3
  const NOTE_TRASH_RETENTION_DAYS = 30;
4
4
  const RECOVERY_RETENTION_BATCH_LIMIT = 100;
5
5
  const SNAPSHOT_RETENTION_DAYS = NOTE_SNAPSHOT_RETENTION_DAYS;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/recovery-retention.ts"],"sourcesContent":["export const NOTE_SNAPSHOT_RETENTION_DAYS = 14;\nexport const NOTE_SNAPSHOT_MAX_PER_NOTE = 10;\nexport const NOTE_TRASH_RETENTION_DAYS = 30;\nexport const RECOVERY_RETENTION_BATCH_LIMIT = 100;\n\nexport const SNAPSHOT_RETENTION_DAYS = NOTE_SNAPSHOT_RETENTION_DAYS;\nexport const SNAPSHOT_MAX_PER_NOTE = NOTE_SNAPSHOT_MAX_PER_NOTE;\nexport const TRASH_RETENTION_DAYS = NOTE_TRASH_RETENTION_DAYS;\nexport const RECOVERY_CLEANUP_BATCH_LIMIT = RECOVERY_RETENTION_BATCH_LIMIT;\n\nconst DAY_IN_MS = 24 * 60 * 60 * 1000;\n\nexport const resolveRetentionCutoff = (retentionDays: number, now = new Date()) => {\n return new Date(now.getTime() - (retentionDays * DAY_IN_MS));\n};\n\nexport const createRetentionCutoff = resolveRetentionCutoff;\n"],"mappings":"AAAO,MAAM,+BAA+B;AACrC,MAAM,6BAA6B;AACnC,MAAM,4BAA4B;AAClC,MAAM,iCAAiC;AAEvC,MAAM,0BAA0B;AAChC,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAC7B,MAAM,+BAA+B;AAE5C,MAAM,YAAY,KAAK,KAAK,KAAK;AAE1B,MAAM,yBAAyB,CAAC,eAAuB,MAAM,oBAAI,KAAK,MAAM;AAC/E,SAAO,IAAI,KAAK,IAAI,QAAQ,IAAK,gBAAgB,SAAU;AAC/D;AAEO,MAAM,wBAAwB;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/recovery-retention.ts"],"sourcesContent":["export const NOTE_SNAPSHOT_RETENTION_DAYS = 14;\nexport const NOTE_SNAPSHOT_MAX_PER_NOTE = 5;\nexport const NOTE_TRASH_RETENTION_DAYS = 30;\nexport const RECOVERY_RETENTION_BATCH_LIMIT = 100;\n\nexport const SNAPSHOT_RETENTION_DAYS = NOTE_SNAPSHOT_RETENTION_DAYS;\nexport const SNAPSHOT_MAX_PER_NOTE = NOTE_SNAPSHOT_MAX_PER_NOTE;\nexport const TRASH_RETENTION_DAYS = NOTE_TRASH_RETENTION_DAYS;\nexport const RECOVERY_CLEANUP_BATCH_LIMIT = RECOVERY_RETENTION_BATCH_LIMIT;\n\nconst DAY_IN_MS = 24 * 60 * 60 * 1000;\n\nexport const resolveRetentionCutoff = (retentionDays: number, now = new Date()) => {\n return new Date(now.getTime() - retentionDays * DAY_IN_MS);\n};\n\nexport const createRetentionCutoff = resolveRetentionCutoff;\n"],"mappings":"AAAO,MAAM,+BAA+B;AACrC,MAAM,6BAA6B;AACnC,MAAM,4BAA4B;AAClC,MAAM,iCAAiC;AAEvC,MAAM,0BAA0B;AAChC,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAC7B,MAAM,+BAA+B;AAE5C,MAAM,YAAY,KAAK,KAAK,KAAK;AAE1B,MAAM,yBAAyB,CAAC,eAAuB,MAAM,oBAAI,KAAK,MAAM;AAC/E,SAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,gBAAgB,SAAS;AAC7D;AAEO,MAAM,wBAAwB;","names":[]}
@@ -0,0 +1,34 @@
1
+ import { serializeServerEvent, subscribeServerEvents } from "./server-events.js";
2
+ const KEEP_ALIVE_INTERVAL_MS = 3e4;
3
+ const createServerEventsHandler = () => {
4
+ return async (req, res) => {
5
+ res.status(200);
6
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
7
+ res.setHeader("Cache-Control", "no-cache, no-transform");
8
+ res.setHeader("Connection", "keep-alive");
9
+ res.setHeader("X-Accel-Buffering", "no");
10
+ res.flushHeaders?.();
11
+ res.write(": connected\n\n");
12
+ const unsubscribe = subscribeServerEvents((event) => {
13
+ res.write(serializeServerEvent(event));
14
+ });
15
+ const keepAliveTimer = setInterval(() => {
16
+ res.write(": keepalive\n\n");
17
+ }, KEEP_ALIVE_INTERVAL_MS);
18
+ let cleanedUp = false;
19
+ const cleanup = () => {
20
+ if (cleanedUp) {
21
+ return;
22
+ }
23
+ cleanedUp = true;
24
+ clearInterval(keepAliveTimer);
25
+ unsubscribe();
26
+ };
27
+ req.on("close", cleanup);
28
+ res.on("close", cleanup);
29
+ };
30
+ };
31
+ export {
32
+ createServerEventsHandler
33
+ };
34
+ //# sourceMappingURL=server-events-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/modules/server-events-handler.ts"],"sourcesContent":["import type { Controller } from '~/types/index.js';\nimport { serializeServerEvent, subscribeServerEvents } from './server-events.js';\n\nconst KEEP_ALIVE_INTERVAL_MS = 30_000;\n\nexport const createServerEventsHandler = (): Controller => {\n return async (req, res) => {\n res.status(200);\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders?.();\n res.write(': connected\\n\\n');\n\n const unsubscribe = subscribeServerEvents((event) => {\n res.write(serializeServerEvent(event));\n });\n\n const keepAliveTimer = setInterval(() => {\n res.write(': keepalive\\n\\n');\n }, KEEP_ALIVE_INTERVAL_MS);\n\n let cleanedUp = false;\n\n const cleanup = () => {\n if (cleanedUp) {\n return;\n }\n\n cleanedUp = true;\n clearInterval(keepAliveTimer);\n unsubscribe();\n };\n\n req.on('close', cleanup);\n res.on('close', cleanup);\n };\n};\n"],"mappings":"AACA,SAAS,sBAAsB,6BAA6B;AAE5D,MAAM,yBAAyB;AAExB,MAAM,4BAA4B,MAAkB;AACvD,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,OAAO,GAAG;AACd,QAAI,UAAU,gBAAgB,kCAAkC;AAChE,QAAI,UAAU,iBAAiB,wBAAwB;AACvD,QAAI,UAAU,cAAc,YAAY;AACxC,QAAI,UAAU,qBAAqB,IAAI;AACvC,QAAI,eAAe;AACnB,QAAI,MAAM,iBAAiB;AAE3B,UAAM,cAAc,sBAAsB,CAAC,UAAU;AACjD,UAAI,MAAM,qBAAqB,KAAK,CAAC;AAAA,IACzC,CAAC;AAED,UAAM,iBAAiB,YAAY,MAAM;AACrC,UAAI,MAAM,iBAAiB;AAAA,IAC/B,GAAG,sBAAsB;AAEzB,QAAI,YAAY;AAEhB,UAAM,UAAU,MAAM;AAClB,UAAI,WAAW;AACX;AAAA,MACJ;AAEA,kBAAY;AACZ,oBAAc,cAAc;AAC5B,kBAAY;AAAA,IAChB;AAEA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AAAA,EAC3B;AACJ;","names":[]}
@@ -0,0 +1,32 @@
1
+ import { EventEmitter } from "node:events";
2
+ const SERVER_EVENT_CHANNEL = "server-event";
3
+ const serverEventEmitter = new EventEmitter();
4
+ serverEventEmitter.setMaxListeners(0);
5
+ let nextServerEventId = 1;
6
+ const emitServerEvent = (event) => {
7
+ const nextEvent = {
8
+ id: String(nextServerEventId++),
9
+ ...event
10
+ };
11
+ serverEventEmitter.emit(SERVER_EVENT_CHANNEL, nextEvent);
12
+ return nextEvent;
13
+ };
14
+ const subscribeServerEvents = (listener) => {
15
+ serverEventEmitter.on(SERVER_EVENT_CHANNEL, listener);
16
+ return () => {
17
+ serverEventEmitter.off(SERVER_EVENT_CHANNEL, listener);
18
+ };
19
+ };
20
+ const serializeServerEvent = (event) => {
21
+ return `id: ${event.id}
22
+ event: ${event.type}
23
+ data: ${JSON.stringify(event)}
24
+
25
+ `;
26
+ };
27
+ export {
28
+ emitServerEvent,
29
+ serializeServerEvent,
30
+ subscribeServerEvents
31
+ };
32
+ //# sourceMappingURL=server-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/modules/server-events.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\n\nexport type McpNoteServerEventType = 'mcp.note.created' | 'mcp.note.updated' | 'mcp.note.deleted';\n\ninterface BaseMcpNoteServerEvent {\n id: string;\n noteId: string;\n source: 'mcp';\n}\n\nexport interface McpNoteCreatedServerEvent extends BaseMcpNoteServerEvent {\n type: 'mcp.note.created';\n updatedAt: string;\n}\n\nexport interface McpNoteUpdatedServerEvent extends BaseMcpNoteServerEvent {\n type: 'mcp.note.updated';\n updatedAt: string;\n}\n\nexport interface McpNoteDeletedServerEvent extends BaseMcpNoteServerEvent {\n type: 'mcp.note.deleted';\n}\n\nexport type ServerEvent = McpNoteCreatedServerEvent | McpNoteUpdatedServerEvent | McpNoteDeletedServerEvent;\n\nexport type ServerEventInput =\n | Omit<McpNoteCreatedServerEvent, 'id'>\n | Omit<McpNoteUpdatedServerEvent, 'id'>\n | Omit<McpNoteDeletedServerEvent, 'id'>;\n\ntype ServerEventListener = (event: ServerEvent) => void;\n\nconst SERVER_EVENT_CHANNEL = 'server-event';\n\nconst serverEventEmitter = new EventEmitter();\n\nserverEventEmitter.setMaxListeners(0);\n\nlet nextServerEventId = 1;\n\nexport const emitServerEvent = (event: ServerEventInput) => {\n const nextEvent = {\n id: String(nextServerEventId++),\n ...event,\n } as ServerEvent;\n\n serverEventEmitter.emit(SERVER_EVENT_CHANNEL, nextEvent);\n\n return nextEvent;\n};\n\nexport const subscribeServerEvents = (listener: ServerEventListener) => {\n serverEventEmitter.on(SERVER_EVENT_CHANNEL, listener);\n\n return () => {\n serverEventEmitter.off(SERVER_EVENT_CHANNEL, listener);\n };\n};\n\nexport const serializeServerEvent = (event: ServerEvent) => {\n return `id: ${event.id}\\nevent: ${event.type}\\ndata: ${JSON.stringify(event)}\\n\\n`;\n};\n"],"mappings":"AAAA,SAAS,oBAAoB;AAiC7B,MAAM,uBAAuB;AAE7B,MAAM,qBAAqB,IAAI,aAAa;AAE5C,mBAAmB,gBAAgB,CAAC;AAEpC,IAAI,oBAAoB;AAEjB,MAAM,kBAAkB,CAAC,UAA4B;AACxD,QAAM,YAAY;AAAA,IACd,IAAI,OAAO,mBAAmB;AAAA,IAC9B,GAAG;AAAA,EACP;AAEA,qBAAmB,KAAK,sBAAsB,SAAS;AAEvD,SAAO;AACX;AAEO,MAAM,wBAAwB,CAAC,aAAkC;AACpE,qBAAmB,GAAG,sBAAsB,QAAQ;AAEpD,SAAO,MAAM;AACT,uBAAmB,IAAI,sBAAsB,QAAQ;AAAA,EACzD;AACJ;AAEO,MAAM,uBAAuB,CAAC,UAAuB;AACxD,SAAO,OAAO,MAAM,EAAE;AAAA,SAAY,MAAM,IAAI;AAAA,QAAW,KAAK,UAAU,KAAK,CAAC;AAAA;AAAA;AAChF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/use-async.ts"],"sourcesContent":["import type {\n Request,\n Response,\n NextFunction\n} from 'express';\nimport type { Controller } from '~/types/index.js';\n\nexport default function useAsync(callback: Controller) {\n return function (req: Request, res: Response, next: NextFunction) {\n void Promise.resolve()\n .then(() => callback(req, res, next))\n .catch((error: unknown) => {\n next(error);\n });\n };\n}\n"],"mappings":"AAOe,SAAR,SAA0B,UAAsB;AACnD,SAAO,SAAU,KAAc,KAAe,MAAoB;AAC9D,SAAK,QAAQ,QAAQ,EAChB,KAAK,MAAM,SAAS,KAAK,KAAK,IAAI,CAAC,EACnC,MAAM,CAAC,UAAmB;AACvB,WAAK,KAAK;AAAA,IACd,CAAC;AAAA,EACT;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/use-async.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\nimport type { Controller } from '~/types/index.js';\n\nexport default function useAsync(callback: Controller) {\n return function (req: Request, res: Response, next: NextFunction) {\n void Promise.resolve()\n .then(() => callback(req, res, next))\n .catch((error: unknown) => {\n next(error);\n });\n };\n}\n"],"mappings":"AAGe,SAAR,SAA0B,UAAsB;AACnD,SAAO,SAAU,KAAc,KAAe,MAAoB;AAC9D,SAAK,QAAQ,QAAQ,EAChB,KAAK,MAAM,SAAS,KAAK,KAAK,IAAI,CAAC,EACnC,MAAM,CAAC,UAAmB;AACvB,WAAK,KAAK;AAAA,IACd,CAAC;AAAA,EACT;AACJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 패키지 루트 (dist/paths.js 기준 상위)\nconst PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT\n || path.resolve(__dirname, '..');\n\n// 이미지 디렉토리 (별도 지정 가능, Docker에서 분리 볼륨용)\nconst IMAGE_DIR = process.env.OCEAN_BRAIN_IMAGE_DIR\n || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || '.', 'assets/images');\n\nexport const paths = {\n clientDist: path.resolve(PACKAGE_ROOT, 'client/dist'),\n clientIndex: path.resolve(PACKAGE_ROOT, 'client/dist/index.html'),\n imageDir: path.resolve(IMAGE_DIR)\n};\n"],"mappings":"AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,MAAM,eAAe,QAAQ,IAAI,4BAC1B,KAAK,QAAQ,WAAW,IAAI;AAGnC,MAAM,YAAY,QAAQ,IAAI,yBACvB,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK,eAAe;AAErE,MAAM,QAAQ;AAAA,EACjB,YAAY,KAAK,QAAQ,cAAc,aAAa;AAAA,EACpD,aAAa,KAAK,QAAQ,cAAc,wBAAwB;AAAA,EAChE,UAAU,KAAK,QAAQ,SAAS;AACpC;","names":[]}
1
+ {"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 패키지 루트 (dist/paths.js 기준 상위)\nconst PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '..');\n\n// 이미지 디렉토리 (별도 지정 가능, Docker에서 분리 볼륨용)\nconst IMAGE_DIR =\n process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || '.', 'assets/images');\n\nexport const paths = {\n clientDist: path.resolve(PACKAGE_ROOT, 'client/dist'),\n clientIndex: path.resolve(PACKAGE_ROOT, 'client/dist/index.html'),\n imageDir: path.resolve(IMAGE_DIR),\n};\n"],"mappings":"AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,MAAM,eAAe,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,IAAI;AAGzF,MAAM,YACF,QAAQ,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK,eAAe;AAEvG,MAAM,QAAQ;AAAA,EACjB,YAAY,KAAK,QAAQ,cAAc,aAAa;AAAA,EACpD,aAAa,KAAK,QAAQ,cAAc,wBAAwB;AAAA,EAChE,UAAU,KAAK,QAAQ,SAAS;AACpC;","names":[]}
@@ -0,0 +1,34 @@
1
+ import { Router } from "express";
2
+ import { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from "../features/auth/http/api.js";
3
+ import { createUploadImageFromSrcHandler, createUploadImageHandler } from "../features/image/http/upload.js";
4
+ import {
5
+ createMcpAdminRevokeTokenHandler,
6
+ createMcpAdminRotateTokenHandler,
7
+ createMcpAdminSetEnabledHandler,
8
+ createMcpAdminStatusHandler
9
+ } from "../features/mcp-admin/http/handlers.js";
10
+ import { requireSessionForWrite } from "../modules/auth-guard.js";
11
+ import { createServerEventsHandler } from "../modules/server-events-handler.js";
12
+ import useAsync from "../modules/use-async.js";
13
+ import { createMcpRouter } from "./mcp.js";
14
+ const createApiRouter = (authConfig, mcpAdminService) => Router().use("/mcp", createMcpRouter(authConfig, mcpAdminService)).post("/auth/login", useAsync(createLoginHandler(authConfig))).post("/auth/logout", useAsync(createLogoutHandler(authConfig))).get("/auth/session", useAsync(createSessionStatusHandler(authConfig))).get(
15
+ "/mcp-admin/status",
16
+ requireSessionForWrite(authConfig),
17
+ useAsync(createMcpAdminStatusHandler(mcpAdminService))
18
+ ).post(
19
+ "/mcp-admin/enabled",
20
+ requireSessionForWrite(authConfig),
21
+ useAsync(createMcpAdminSetEnabledHandler(mcpAdminService))
22
+ ).post(
23
+ "/mcp-admin/token/rotate",
24
+ requireSessionForWrite(authConfig),
25
+ useAsync(createMcpAdminRotateTokenHandler(mcpAdminService))
26
+ ).post(
27
+ "/mcp-admin/token/revoke",
28
+ requireSessionForWrite(authConfig),
29
+ useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService))
30
+ ).post("/image", requireSessionForWrite(authConfig), useAsync(createUploadImageHandler())).post("/image-from-src", requireSessionForWrite(authConfig), useAsync(createUploadImageFromSrcHandler())).get("/events", requireSessionForWrite(authConfig), createServerEventsHandler());
31
+ export {
32
+ createApiRouter
33
+ };
34
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/api.ts"],"sourcesContent":["import { Router } from 'express';\nimport { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from '../features/auth/http/api.js';\nimport { createUploadImageFromSrcHandler, createUploadImageHandler } from '../features/image/http/upload.js';\nimport {\n createMcpAdminRevokeTokenHandler,\n createMcpAdminRotateTokenHandler,\n createMcpAdminSetEnabledHandler,\n createMcpAdminStatusHandler,\n} from '../features/mcp-admin/http/handlers.js';\nimport type { McpAdminService } from '../features/mcp-admin/service.js';\nimport { requireSessionForWrite } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createServerEventsHandler } from '../modules/server-events-handler.js';\nimport useAsync from '../modules/use-async.js';\nimport { createMcpRouter } from './mcp.js';\n\ntype McpAdminApiService = Pick<\n McpAdminService,\n 'getStatus' | 'setEnabled' | 'rotateToken' | 'revokeActiveToken' | 'validatePresentedToken'\n>;\n\nexport const createApiRouter = (authConfig: AuthConfig, mcpAdminService: McpAdminApiService) =>\n Router()\n .use('/mcp', createMcpRouter(authConfig, mcpAdminService))\n .post('/auth/login', useAsync(createLoginHandler(authConfig)))\n .post('/auth/logout', useAsync(createLogoutHandler(authConfig)))\n .get('/auth/session', useAsync(createSessionStatusHandler(authConfig)))\n .get(\n '/mcp-admin/status',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminStatusHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/enabled',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminSetEnabledHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/rotate',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminRotateTokenHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/revoke',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService)),\n )\n .post('/image', requireSessionForWrite(authConfig), useAsync(createUploadImageHandler()))\n .post('/image-from-src', requireSessionForWrite(authConfig), useAsync(createUploadImageFromSrcHandler()))\n .get('/events', requireSessionForWrite(authConfig), createServerEventsHandler());\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,oBAAoB,qBAAqB,kCAAkC;AACpF,SAAS,iCAAiC,gCAAgC;AAC1E;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEP,SAAS,8BAA8B;AAEvC,SAAS,iCAAiC;AAC1C,OAAO,cAAc;AACrB,SAAS,uBAAuB;AAOzB,MAAM,kBAAkB,CAAC,YAAwB,oBACpD,OAAO,EACF,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,KAAK,eAAe,SAAS,mBAAmB,UAAU,CAAC,CAAC,EAC5D,KAAK,gBAAgB,SAAS,oBAAoB,UAAU,CAAC,CAAC,EAC9D,IAAI,iBAAiB,SAAS,2BAA2B,UAAU,CAAC,CAAC,EACrE;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,4BAA4B,eAAe,CAAC;AACzD,EACC;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,gCAAgC,eAAe,CAAC;AAC7D,EACC;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,iCAAiC,eAAe,CAAC;AAC9D,EACC;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,iCAAiC,eAAe,CAAC;AAC9D,EACC,KAAK,UAAU,uBAAuB,UAAU,GAAG,SAAS,yBAAyB,CAAC,CAAC,EACvF,KAAK,mBAAmB,uBAAuB,UAAU,GAAG,SAAS,gCAAgC,CAAC,CAAC,EACvG,IAAI,WAAW,uBAAuB,UAAU,GAAG,0BAA0B,CAAC;","names":[]}
@@ -0,0 +1,11 @@
1
+ import { Router } from "express";
2
+ import {
3
+ createLoginPageHandler,
4
+ createLoginPageSubmitHandler,
5
+ createLogoutPageHandler
6
+ } from "../features/auth/http/pages.js";
7
+ const createAuthPagesRouter = (authConfig) => Router().get("/login", createLoginPageHandler(authConfig)).post("/login", createLoginPageSubmitHandler(authConfig)).post("/logout", createLogoutPageHandler(authConfig));
8
+ export {
9
+ createAuthPagesRouter
10
+ };
11
+ //# sourceMappingURL=auth-pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/auth-pages.ts"],"sourcesContent":["import { Router } from 'express';\nimport {\n createLoginPageHandler,\n createLoginPageSubmitHandler,\n createLogoutPageHandler,\n} from '../features/auth/http/pages.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\n\nexport const createAuthPagesRouter = (authConfig: AuthConfig) =>\n Router()\n .get('/login', createLoginPageHandler(authConfig))\n .post('/login', createLoginPageSubmitHandler(authConfig))\n .post('/logout', createLogoutPageHandler(authConfig));\n"],"mappings":"AAAA,SAAS,cAAc;AACvB;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAGA,MAAM,wBAAwB,CAAC,eAClC,OAAO,EACF,IAAI,UAAU,uBAAuB,UAAU,CAAC,EAChD,KAAK,UAAU,6BAA6B,UAAU,CAAC,EACvD,KAAK,WAAW,wBAAwB,UAAU,CAAC;","names":[]}
@@ -0,0 +1,27 @@
1
+ import express, { Router } from "express";
2
+ import path from "path";
3
+ import { isAuthenticatedRequest } from "../modules/auth-guard.js";
4
+ import { paths } from "../paths.js";
5
+ const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
6
+ if (authConfig.mode !== "password" || authenticated) {
7
+ return false;
8
+ }
9
+ if (requestPath.startsWith("/api") || requestPath.startsWith("/graphql") || requestPath.startsWith("/auth")) {
10
+ return false;
11
+ }
12
+ return path.extname(requestPath) === "";
13
+ };
14
+ const createClientRouter = (authConfig) => Router().use("/assets/images", express.static(paths.imageDir)).use((req, res, next) => {
15
+ if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {
16
+ const redirectPath = encodeURIComponent(req.originalUrl || "/");
17
+ res.redirect(303, `/auth/login?next=${redirectPath}`);
18
+ return;
19
+ }
20
+ next();
21
+ }).use(express.static(paths.clientDist, { extensions: ["html"] })).get(/.*/, (_req, res) => {
22
+ res.sendFile(paths.clientIndex);
23
+ });
24
+ export {
25
+ createClientRouter
26
+ };
27
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/client.ts"],"sourcesContent":["import express, { Router } from 'express';\nimport path from 'path';\nimport { isAuthenticatedRequest } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { paths } from '../paths.js';\n\nconst shouldBlockClientRoute = (authConfig: AuthConfig, requestPath: string, authenticated: boolean) => {\n if (authConfig.mode !== 'password' || authenticated) {\n return false;\n }\n\n if (requestPath.startsWith('/api') || requestPath.startsWith('/graphql') || requestPath.startsWith('/auth')) {\n return false;\n }\n\n return path.extname(requestPath) === '';\n};\n\nexport const createClientRouter = (authConfig: AuthConfig) =>\n Router()\n .use('/assets/images', express.static(paths.imageDir))\n .use((req, res, next) => {\n if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {\n const redirectPath = encodeURIComponent(req.originalUrl || '/');\n res.redirect(303, `/auth/login?next=${redirectPath}`);\n return;\n }\n\n next();\n })\n .use(express.static(paths.clientDist, { extensions: ['html'] }))\n .get(/.*/, (_req, res) => {\n res.sendFile(paths.clientIndex);\n });\n"],"mappings":"AAAA,OAAO,WAAW,cAAc;AAChC,OAAO,UAAU;AACjB,SAAS,8BAA8B;AAEvC,SAAS,aAAa;AAEtB,MAAM,yBAAyB,CAAC,YAAwB,aAAqB,kBAA2B;AACpG,MAAI,WAAW,SAAS,cAAc,eAAe;AACjD,WAAO;AAAA,EACX;AAEA,MAAI,YAAY,WAAW,MAAM,KAAK,YAAY,WAAW,UAAU,KAAK,YAAY,WAAW,OAAO,GAAG;AACzG,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,QAAQ,WAAW,MAAM;AACzC;AAEO,MAAM,qBAAqB,CAAC,eAC/B,OAAO,EACF,IAAI,kBAAkB,QAAQ,OAAO,MAAM,QAAQ,CAAC,EACpD,IAAI,CAAC,KAAK,KAAK,SAAS;AACrB,MAAI,uBAAuB,YAAY,IAAI,MAAM,uBAAuB,GAAG,CAAC,GAAG;AAC3E,UAAM,eAAe,mBAAmB,IAAI,eAAe,GAAG;AAC9D,QAAI,SAAS,KAAK,oBAAoB,YAAY,EAAE;AACpD;AAAA,EACJ;AAEA,OAAK;AACT,CAAC,EACA,IAAI,QAAQ,OAAO,MAAM,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,EAC9D,IAAI,MAAM,CAAC,MAAM,QAAQ;AACtB,MAAI,SAAS,MAAM,WAAW;AAClC,CAAC;","names":[]}