ocean-brain 0.3.3 → 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/dist/mcp.js +151 -37
  2. package/package.json +5 -5
  3. package/scripts/postinstall-prisma.mjs +3 -2
  4. package/server/client/dist/assets/Calendar-CSXEuWmQ.js +25 -0
  5. package/server/client/dist/assets/Callout-Ap1OcxWN.js +1 -0
  6. package/server/client/dist/assets/Graph-C0Ax9qjI.js +1 -0
  7. package/server/client/dist/assets/{Image-ZxF459sC.js → Image-CSbcG-5a.js} +1 -1
  8. package/server/client/dist/assets/{Image.es-CFTbON7k.js → Image.es-Fbe7aDDo.js} +1 -1
  9. package/server/client/dist/assets/{ModalActionRow-Hg6MY61u.js → ModalActionRow-DjI5b4bl.js} +1 -1
  10. package/server/client/dist/assets/Note-BY6WgYLq.js +1 -0
  11. package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
  12. package/server/client/dist/assets/Reminders-vRksvSQE.js +1 -0
  13. package/server/client/dist/assets/Search-Cq4tuiJO.js +1 -0
  14. package/server/client/dist/assets/{SurfaceCard-BV6y4bCo.js → SurfaceCard-Br6TjX5W.js} +1 -1
  15. package/server/client/dist/assets/Tag-ARDLA-Pb.js +1 -0
  16. package/server/client/dist/assets/TagNotes-CDGVrCJU.js +1 -0
  17. package/server/client/dist/assets/{Trash.es-CPtRLE0y.js → Trash.es-BfCEiL3-.js} +1 -1
  18. package/server/client/dist/assets/ViewNotes-ZBOQ1adH.js +1 -0
  19. package/server/client/dist/assets/Views-DQHPfdwM.js +1 -0
  20. package/server/client/dist/assets/{image.api-Dsrx9S-G.js → image.api-DGXlQ_Ca.js} +1 -1
  21. package/server/client/dist/assets/index-B28TO4FC.js +1 -0
  22. package/server/client/dist/assets/{index-CD2bFEqK.css → index-Bm9l_L20.css} +1 -1
  23. package/server/client/dist/assets/index-Bsx4P7EA.js +210 -0
  24. package/server/client/dist/assets/manage-image-Dg297A_h.js +1 -0
  25. package/server/client/dist/assets/manage-image-detail-hk6jWyiB.js +1 -0
  26. package/server/client/dist/assets/mcp-CSjmBjFR.js +1 -0
  27. package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
  28. package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-5N5mRkIT.js} +3 -3
  29. package/server/client/dist/assets/{placeholder-MNsco0AT.js → placeholder-Bmiowo28.js} +4 -4
  30. package/server/client/dist/assets/trash-CM7SbVtW.js +1 -0
  31. package/server/client/dist/assets/{useReminderMutate-yJak8FzP.js → useReminderMutate-DsIvVpCQ.js} +2 -2
  32. package/server/client/dist/assets/view-dashboard-CP8nhfxQ.js +90 -0
  33. package/server/client/dist/index.html +4 -4
  34. package/server/dist/app.js +8 -84
  35. package/server/dist/app.js.map +1 -1
  36. package/server/dist/features/auth/http/api.js +43 -0
  37. package/server/dist/features/auth/http/api.js.map +1 -0
  38. package/server/dist/features/auth/http/login-page.js +28 -0
  39. package/server/dist/features/auth/http/login-page.js.map +1 -0
  40. package/server/dist/features/auth/http/pages.js +50 -0
  41. package/server/dist/features/auth/http/pages.js.map +1 -0
  42. package/server/dist/features/auth/service.js +94 -0
  43. package/server/dist/features/auth/service.js.map +1 -0
  44. package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
  45. package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
  46. package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
  47. package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
  48. package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
  49. package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
  50. package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
  51. package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
  52. package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
  53. package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
  54. package/server/dist/features/cache/graphql/index.js +21 -0
  55. package/server/dist/features/cache/graphql/index.js.map +1 -0
  56. package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
  57. package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
  58. package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
  59. package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
  60. package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
  61. package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
  62. package/server/dist/features/image/graphql/image.resolver.js +12 -0
  63. package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
  64. package/server/dist/features/image/graphql/image.type-defs.js +31 -0
  65. package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
  66. package/server/dist/features/image/graphql/index.js +16 -0
  67. package/server/dist/features/image/graphql/index.js.map +1 -0
  68. package/server/dist/features/image/http/upload.js +48 -0
  69. package/server/dist/features/image/http/upload.js.map +1 -0
  70. package/server/dist/features/image/services/delete.js +52 -0
  71. package/server/dist/features/image/services/delete.js.map +1 -0
  72. package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
  73. package/server/dist/features/image/services/remote-fetch.js.map +1 -0
  74. package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
  75. package/server/dist/features/image/services/upload.js.map +1 -0
  76. package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
  77. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
  78. package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
  79. package/server/dist/features/mcp-admin/service.js.map +1 -0
  80. package/server/dist/features/note/graphql/index.js +8 -0
  81. package/server/dist/features/note/graphql/index.js.map +1 -0
  82. package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
  83. package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
  84. package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
  85. package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
  86. package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
  87. package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
  88. package/server/dist/features/note/graphql/note.query.resolver.js +382 -0
  89. package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
  90. package/server/dist/features/note/graphql/note.resolver.js +14 -0
  91. package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
  92. package/server/dist/features/note/graphql/note.type-defs.js +184 -0
  93. package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
  94. package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
  95. package/server/dist/features/note/http/mcp.js.map +1 -0
  96. package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
  97. package/server/dist/features/note/services/authoring.js.map +1 -0
  98. package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
  99. package/server/dist/features/note/services/cleanup.js.map +1 -0
  100. package/server/dist/features/note/services/search-backfill.js +84 -0
  101. package/server/dist/features/note/services/search-backfill.js.map +1 -0
  102. package/server/dist/features/note/services/search.js +197 -0
  103. package/server/dist/features/note/services/search.js.map +1 -0
  104. package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
  105. package/server/dist/features/note/services/snapshot.js.map +1 -0
  106. package/server/dist/features/note/services/tag-filter.js +18 -0
  107. package/server/dist/features/note/services/tag-filter.js.map +1 -0
  108. package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +28 -23
  109. package/server/dist/features/note/services/trash.js.map +1 -0
  110. package/server/dist/features/placeholder/graphql/index.js +27 -0
  111. package/server/dist/features/placeholder/graphql/index.js.map +1 -0
  112. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
  113. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
  114. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
  115. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
  116. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
  117. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
  118. package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
  119. package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
  120. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
  121. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
  122. package/server/dist/features/reminder/graphql/index.js +27 -0
  123. package/server/dist/features/reminder/graphql/index.js.map +1 -0
  124. package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
  125. package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
  126. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
  127. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
  128. package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
  129. package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
  130. package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
  131. package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
  132. package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
  133. package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
  134. package/server/dist/features/tag/graphql/index.js +14 -0
  135. package/server/dist/features/tag/graphql/index.js.map +1 -0
  136. package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
  137. package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
  138. package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
  139. package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
  140. package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
  141. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
  142. package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
  143. package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
  144. package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
  145. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
  146. package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
  147. package/server/dist/features/tag/http/mcp.js.map +1 -0
  148. package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
  149. package/server/dist/features/tag/services/organization.js.map +1 -0
  150. package/server/dist/features/view/graphql/index.js +11 -0
  151. package/server/dist/features/view/graphql/index.js.map +1 -0
  152. package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
  153. package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
  154. package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
  155. package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
  156. package/server/dist/features/view/graphql/view.resolver.js +10 -0
  157. package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
  158. package/server/dist/features/view/graphql/view.type-defs.js +63 -0
  159. package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
  160. package/server/dist/features/view/services/workspace.js +432 -0
  161. package/server/dist/features/view/services/workspace.js.map +1 -0
  162. package/server/dist/main.js +14 -0
  163. package/server/dist/main.js.map +1 -1
  164. package/server/dist/modules/auth-guard.js +10 -9
  165. package/server/dist/modules/auth-guard.js.map +1 -1
  166. package/server/dist/modules/auth-mode.js +1 -3
  167. package/server/dist/modules/auth-mode.js.map +1 -1
  168. package/server/dist/modules/blocknote.js +96 -33
  169. package/server/dist/modules/blocknote.js.map +1 -1
  170. package/server/dist/modules/data-maintenance.js +75 -0
  171. package/server/dist/modules/data-maintenance.js.map +1 -0
  172. package/server/dist/modules/error-handler.js.map +1 -1
  173. package/server/dist/modules/logger.js +5 -23
  174. package/server/dist/modules/logger.js.map +1 -1
  175. package/server/dist/modules/mcp-auth.js +4 -5
  176. package/server/dist/modules/mcp-auth.js.map +1 -1
  177. package/server/dist/modules/mcp-token.js.map +1 -1
  178. package/server/dist/modules/prisma-runtime.js +2 -7
  179. package/server/dist/modules/prisma-runtime.js.map +1 -1
  180. package/server/dist/modules/recovery-retention.js +1 -1
  181. package/server/dist/modules/recovery-retention.js.map +1 -1
  182. package/server/dist/modules/server-events-handler.js +34 -0
  183. package/server/dist/modules/server-events-handler.js.map +1 -0
  184. package/server/dist/modules/server-events.js +32 -0
  185. package/server/dist/modules/server-events.js.map +1 -0
  186. package/server/dist/modules/use-async.js.map +1 -1
  187. package/server/dist/paths.js.map +1 -1
  188. package/server/dist/routes/api.js +34 -0
  189. package/server/dist/routes/api.js.map +1 -0
  190. package/server/dist/routes/auth-pages.js +11 -0
  191. package/server/dist/routes/auth-pages.js.map +1 -0
  192. package/server/dist/routes/client.js +27 -0
  193. package/server/dist/routes/client.js.map +1 -0
  194. package/server/dist/routes/graphql.js +33 -0
  195. package/server/dist/routes/graphql.js.map +1 -0
  196. package/server/dist/routes/index.js +11 -0
  197. package/server/dist/routes/index.js.map +1 -0
  198. package/server/dist/routes/mcp.js +30 -0
  199. package/server/dist/routes/mcp.js.map +1 -0
  200. package/server/dist/schema/index.js +11 -8
  201. package/server/dist/schema/index.js.map +1 -1
  202. package/server/dist/types/index.js.map +1 -1
  203. package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
  204. package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
  205. package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
  206. package/server/prisma/schema.prisma +66 -12
  207. package/server/client/dist/assets/Calendar-DyXzs5M3.js +0 -25
  208. package/server/client/dist/assets/Graph-C8K4PJOn.js +0 -1
  209. package/server/client/dist/assets/Note-BbgZvrC3.js +0 -1
  210. package/server/client/dist/assets/Note-PEevDKBh.css +0 -1
  211. package/server/client/dist/assets/Reminders-jFhljND4.js +0 -1
  212. package/server/client/dist/assets/Search-X6QIR_m2.js +0 -1
  213. package/server/client/dist/assets/Tag-Ba2Dw4gH.js +0 -1
  214. package/server/client/dist/assets/TagNotes-BRJv6sDI.js +0 -1
  215. package/server/client/dist/assets/index-BglnLSet.js +0 -205
  216. package/server/client/dist/assets/index-DpqKngJz.js +0 -1
  217. package/server/client/dist/assets/manage-image-C6Y_0M5H.js +0 -1
  218. package/server/client/dist/assets/manage-image-detail-Dv3xofpe.js +0 -1
  219. package/server/client/dist/assets/mcp-BijQkpfY.js +0 -1
  220. package/server/client/dist/assets/trash-CiFJm_PK.js +0 -1
  221. package/server/dist/modules/auth.js +0 -25
  222. package/server/dist/modules/auth.js.map +0 -1
  223. package/server/dist/modules/image-upload.js.map +0 -1
  224. package/server/dist/modules/mcp-admin.js.map +0 -1
  225. package/server/dist/modules/note-authoring.js.map +0 -1
  226. package/server/dist/modules/note-cleanup.js.map +0 -1
  227. package/server/dist/modules/note-snapshot.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 -551
  236. package/server/dist/schema/note/index.js.map +0 -1
  237. package/server/dist/schema/placeholder/index.js +0 -105
  238. package/server/dist/schema/placeholder/index.js.map +0 -1
  239. package/server/dist/schema/reminder/index.js +0 -161
  240. package/server/dist/schema/reminder/index.js.map +0 -1
  241. package/server/dist/schema/tag/index.js +0 -81
  242. package/server/dist/schema/tag/index.js.map +0 -1
  243. package/server/dist/urls.js +0 -9
  244. package/server/dist/urls.js.map +0 -1
  245. package/server/dist/views/auth.js +0 -305
  246. package/server/dist/views/auth.js.map +0 -1
  247. package/server/dist/views/image.js +0 -44
  248. package/server/dist/views/image.js.map +0 -1
  249. package/server/dist/views/index.js +0 -6
  250. package/server/dist/views/index.js.map +0 -1
  251. package/server/dist/views/mcp-admin.js.map +0 -1
  252. package/server/dist/views/note.js.map +0 -1
  253. package/server/dist/views/tag.js.map +0 -1
@@ -0,0 +1,432 @@
1
+ import { buildNoteTagNamesWhere } from "../../../features/note/services/tag-filter.js";
2
+ import models from "../../../models.js";
3
+ const VIEW_WORKSPACE_ID = 1;
4
+ const DEFAULT_VIEW_SECTION_LIMIT = 5;
5
+ const MIN_VIEW_SECTION_LIMIT = 1;
6
+ const MAX_VIEW_SECTION_LIMIT = 20;
7
+ const orderedViewSectionTagsInclude = {
8
+ tags: {
9
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }]
10
+ }
11
+ };
12
+ const orderedViewTabSectionsInclude = {
13
+ sections: {
14
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
15
+ include: orderedViewSectionTagsInclude
16
+ }
17
+ };
18
+ const normalizeTagName = (value) => {
19
+ const trimmedValue = value.trim();
20
+ if (!trimmedValue) {
21
+ return "";
22
+ }
23
+ if (trimmedValue.startsWith("@")) {
24
+ return trimmedValue;
25
+ }
26
+ if (trimmedValue.startsWith("#")) {
27
+ return `@${trimmedValue.slice(1)}`;
28
+ }
29
+ return `@${trimmedValue}`;
30
+ };
31
+ const normalizeViewTagNames = (values) => {
32
+ const normalizedTagNames = values.flatMap((value) => value.split(","));
33
+ return Array.from(new Set(normalizedTagNames.map(normalizeTagName).filter(Boolean)));
34
+ };
35
+ const clampViewSectionLimit = (value) => {
36
+ const numericValue = typeof value === "number" ? value : Number.NaN;
37
+ if (!Number.isInteger(numericValue)) {
38
+ return DEFAULT_VIEW_SECTION_LIMIT;
39
+ }
40
+ return Math.min(MAX_VIEW_SECTION_LIMIT, Math.max(MIN_VIEW_SECTION_LIMIT, numericValue));
41
+ };
42
+ const normalizeViewTabTitle = (title) => {
43
+ const trimmedTitle = title.trim();
44
+ return trimmedTitle || "Untitled View";
45
+ };
46
+ const buildDefaultSectionTitle = (tagNames) => {
47
+ if (tagNames.length === 0) {
48
+ return "Tagged notes";
49
+ }
50
+ return tagNames.slice(0, 2).join(" + ");
51
+ };
52
+ const normalizeViewSectionInput = (input) => {
53
+ const tagNames = normalizeViewTagNames(input.tagNames);
54
+ if (tagNames.length === 0) {
55
+ throw new Error("A view section requires at least one tag.");
56
+ }
57
+ const trimmedTitle = input.title?.trim() ?? "";
58
+ return {
59
+ title: trimmedTitle || buildDefaultSectionTitle(tagNames),
60
+ tagNames,
61
+ mode: input.mode === "or" ? "or" : "and",
62
+ limit: clampViewSectionLimit(input.limit)
63
+ };
64
+ };
65
+ const pickNextActiveViewTabId = (tabIds, deletedTabId, currentActiveTabId) => {
66
+ const remainingTabIds = tabIds.filter((tabId) => tabId !== deletedTabId);
67
+ if (remainingTabIds.length === 0) {
68
+ return null;
69
+ }
70
+ if (currentActiveTabId !== deletedTabId) {
71
+ return currentActiveTabId;
72
+ }
73
+ return remainingTabIds[0] ?? null;
74
+ };
75
+ const ensureValidReorderIds = (currentIds, nextIds) => {
76
+ if (currentIds.length !== nextIds.length) {
77
+ throw new Error("Reorder payload must include every item exactly once.");
78
+ }
79
+ const currentIdSet = new Set(currentIds);
80
+ const nextIdSet = new Set(nextIds);
81
+ if (currentIdSet.size !== currentIds.length || nextIdSet.size !== nextIds.length) {
82
+ throw new Error("Reorder payload contains duplicate ids.");
83
+ }
84
+ for (const id of currentIdSet) {
85
+ if (!nextIdSet.has(id)) {
86
+ throw new Error("Reorder payload must match the current item set.");
87
+ }
88
+ }
89
+ };
90
+ const parseViewId = (id) => {
91
+ const numericId = Number(id);
92
+ if (!Number.isInteger(numericId) || numericId <= 0) {
93
+ throw new Error("A valid id is required.");
94
+ }
95
+ return numericId;
96
+ };
97
+ const serializeViewSection = (section) => ({
98
+ id: String(section.id),
99
+ tabId: String(section.tabId),
100
+ title: section.title,
101
+ tagNames: section.tags.map((tag) => tag.name),
102
+ mode: section.mode,
103
+ limit: section.limit,
104
+ order: section.order
105
+ });
106
+ const serializeViewTab = (tab) => ({
107
+ id: String(tab.id),
108
+ title: tab.title,
109
+ order: tab.order,
110
+ sections: tab.sections.map(serializeViewSection)
111
+ });
112
+ const getOrderedViewTabs = async (db) => {
113
+ return db.viewTab.findMany({
114
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
115
+ include: orderedViewTabSectionsInclude
116
+ });
117
+ };
118
+ const ensureViewWorkspace = async (db) => {
119
+ return db.viewWorkspace.upsert({
120
+ where: { id: VIEW_WORKSPACE_ID },
121
+ update: {},
122
+ create: { id: VIEW_WORKSPACE_ID }
123
+ });
124
+ };
125
+ const readViewWorkspace = async (db) => {
126
+ const [workspace, tabs] = await Promise.all([ensureViewWorkspace(db), getOrderedViewTabs(db)]);
127
+ const activeTabId = workspace.activeTabId !== null && tabs.some((tab) => tab.id === workspace.activeTabId) ? String(workspace.activeTabId) : tabs[0] ? String(tabs[0].id) : null;
128
+ return {
129
+ activeTabId,
130
+ tabs: tabs.map(serializeViewTab)
131
+ };
132
+ };
133
+ const readViewSection = async (db, id) => {
134
+ const section = await db.viewSection.findUnique({
135
+ where: { id },
136
+ include: orderedViewSectionTagsInclude
137
+ });
138
+ return section ? serializeViewSection(section) : null;
139
+ };
140
+ const getNextTabOrder = async (db) => {
141
+ const lastTab = await db.viewTab.findFirst({
142
+ orderBy: [{ order: "desc" }, { createdAt: "desc" }],
143
+ select: { order: true }
144
+ });
145
+ return lastTab ? lastTab.order + 1 : 0;
146
+ };
147
+ const getNextSectionOrder = async (db, tabId) => {
148
+ const lastSection = await db.viewSection.findFirst({
149
+ where: { tabId },
150
+ orderBy: [{ order: "desc" }, { createdAt: "desc" }],
151
+ select: { order: true }
152
+ });
153
+ return lastSection ? lastSection.order + 1 : 0;
154
+ };
155
+ const resequenceViewTabs = async (db) => {
156
+ const tabs = await db.viewTab.findMany({
157
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
158
+ select: { id: true }
159
+ });
160
+ await Promise.all(
161
+ tabs.map(
162
+ (tab, index) => db.viewTab.update({
163
+ where: { id: tab.id },
164
+ data: { order: index }
165
+ })
166
+ )
167
+ );
168
+ };
169
+ const resequenceViewSections = async (db, tabId) => {
170
+ const sections = await db.viewSection.findMany({
171
+ where: { tabId },
172
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
173
+ select: { id: true }
174
+ });
175
+ await Promise.all(
176
+ sections.map(
177
+ (section, index) => db.viewSection.update({
178
+ where: { id: section.id },
179
+ data: { order: index }
180
+ })
181
+ )
182
+ );
183
+ };
184
+ const getViewWorkspace = async () => {
185
+ return readViewWorkspace(models);
186
+ };
187
+ const getViewSectionById = async (id) => {
188
+ return readViewSection(models, parseViewId(id));
189
+ };
190
+ const getViewSectionNotes = async (id, pagination) => {
191
+ const section = await models.viewSection.findUnique({
192
+ where: { id: parseViewId(id) },
193
+ include: orderedViewSectionTagsInclude
194
+ });
195
+ if (!section) {
196
+ return null;
197
+ }
198
+ const tagNames = section.tags.map((tag) => tag.name);
199
+ if (tagNames.length === 0) {
200
+ return {
201
+ totalCount: 0,
202
+ notes: []
203
+ };
204
+ }
205
+ const where = buildNoteTagNamesWhere(tagNames, section.mode);
206
+ const [totalCount, notes] = await Promise.all([
207
+ models.note.count({ where }),
208
+ models.note.findMany({
209
+ orderBy: { updatedAt: "desc" },
210
+ where,
211
+ take: Number(pagination.limit),
212
+ skip: Number(pagination.offset)
213
+ })
214
+ ]);
215
+ return {
216
+ totalCount,
217
+ notes
218
+ };
219
+ };
220
+ const createViewTab = async (title) => {
221
+ return models.$transaction(async (tx) => {
222
+ const workspace = await ensureViewWorkspace(tx);
223
+ const nextTabOrder = await getNextTabOrder(tx);
224
+ const createdTab = await tx.viewTab.create({
225
+ data: {
226
+ title: normalizeViewTabTitle(title),
227
+ order: nextTabOrder
228
+ },
229
+ include: orderedViewTabSectionsInclude
230
+ });
231
+ if (workspace.activeTabId === null) {
232
+ await tx.viewWorkspace.update({
233
+ where: { id: VIEW_WORKSPACE_ID },
234
+ data: { activeTabId: createdTab.id }
235
+ });
236
+ }
237
+ return serializeViewTab(createdTab);
238
+ });
239
+ };
240
+ const updateViewTab = async (id, title) => {
241
+ const updatedTab = await models.viewTab.update({
242
+ where: { id: parseViewId(id) },
243
+ data: {
244
+ title: normalizeViewTabTitle(title)
245
+ },
246
+ include: orderedViewTabSectionsInclude
247
+ });
248
+ return serializeViewTab(updatedTab);
249
+ };
250
+ const deleteViewTab = async (id) => {
251
+ const tabId = parseViewId(id);
252
+ return models.$transaction(async (tx) => {
253
+ const workspace = await ensureViewWorkspace(tx);
254
+ const tabs = await tx.viewTab.findMany({
255
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
256
+ select: { id: true }
257
+ });
258
+ if (!tabs.some((tab) => tab.id === tabId)) {
259
+ return false;
260
+ }
261
+ await tx.viewTab.delete({ where: { id: tabId } });
262
+ await resequenceViewTabs(tx);
263
+ const nextActiveTabId = pickNextActiveViewTabId(
264
+ tabs.map((tab) => tab.id),
265
+ tabId,
266
+ workspace.activeTabId
267
+ );
268
+ await tx.viewWorkspace.update({
269
+ where: { id: VIEW_WORKSPACE_ID },
270
+ data: { activeTabId: nextActiveTabId }
271
+ });
272
+ return true;
273
+ });
274
+ };
275
+ const setActiveViewTab = async (id) => {
276
+ const tabId = parseViewId(id);
277
+ return models.$transaction(async (tx) => {
278
+ const workspace = await ensureViewWorkspace(tx);
279
+ const tab = await tx.viewTab.findUnique({
280
+ where: { id: tabId },
281
+ select: { id: true }
282
+ });
283
+ if (!tab) {
284
+ throw new Error("View tab not found.");
285
+ }
286
+ await tx.viewWorkspace.update({
287
+ where: { id: workspace.id },
288
+ data: { activeTabId: tabId }
289
+ });
290
+ return readViewWorkspace(tx);
291
+ });
292
+ };
293
+ const reorderViewTabs = async (tabIds) => {
294
+ const nextTabIds = tabIds.map(parseViewId);
295
+ return models.$transaction(async (tx) => {
296
+ const tabs = await tx.viewTab.findMany({
297
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
298
+ select: { id: true }
299
+ });
300
+ const currentTabIds = tabs.map((tab) => tab.id);
301
+ ensureValidReorderIds(currentTabIds, nextTabIds);
302
+ await Promise.all(
303
+ nextTabIds.map(
304
+ (tabId, index) => tx.viewTab.update({
305
+ where: { id: tabId },
306
+ data: { order: index }
307
+ })
308
+ )
309
+ );
310
+ const reorderedTabs = await getOrderedViewTabs(tx);
311
+ return reorderedTabs.map(serializeViewTab);
312
+ });
313
+ };
314
+ const createViewSection = async (tabId, input) => {
315
+ const numericTabId = parseViewId(tabId);
316
+ const nextSection = normalizeViewSectionInput(input);
317
+ return models.$transaction(async (tx) => {
318
+ const tab = await tx.viewTab.findUnique({
319
+ where: { id: numericTabId },
320
+ select: { id: true }
321
+ });
322
+ if (!tab) {
323
+ throw new Error("View tab not found.");
324
+ }
325
+ const nextSectionOrder = await getNextSectionOrder(tx, numericTabId);
326
+ const createdSection = await tx.viewSection.create({
327
+ data: {
328
+ tabId: numericTabId,
329
+ title: nextSection.title,
330
+ mode: nextSection.mode,
331
+ limit: nextSection.limit,
332
+ order: nextSectionOrder,
333
+ tags: {
334
+ create: nextSection.tagNames.map((tagName, index) => ({
335
+ name: tagName,
336
+ order: index
337
+ }))
338
+ }
339
+ },
340
+ include: orderedViewSectionTagsInclude
341
+ });
342
+ return serializeViewSection(createdSection);
343
+ });
344
+ };
345
+ const updateViewSection = async (id, input) => {
346
+ const sectionId = parseViewId(id);
347
+ const nextSection = normalizeViewSectionInput(input);
348
+ const updatedSection = await models.viewSection.update({
349
+ where: { id: sectionId },
350
+ data: {
351
+ title: nextSection.title,
352
+ mode: nextSection.mode,
353
+ limit: nextSection.limit,
354
+ tags: {
355
+ deleteMany: {},
356
+ create: nextSection.tagNames.map((tagName, index) => ({
357
+ name: tagName,
358
+ order: index
359
+ }))
360
+ }
361
+ },
362
+ include: orderedViewSectionTagsInclude
363
+ });
364
+ return serializeViewSection(updatedSection);
365
+ };
366
+ const deleteViewSection = async (id) => {
367
+ const sectionId = parseViewId(id);
368
+ return models.$transaction(async (tx) => {
369
+ const section = await tx.viewSection.findUnique({
370
+ where: { id: sectionId },
371
+ select: { id: true, tabId: true }
372
+ });
373
+ if (!section) {
374
+ return false;
375
+ }
376
+ await tx.viewSection.delete({
377
+ where: { id: sectionId }
378
+ });
379
+ await resequenceViewSections(tx, section.tabId);
380
+ return true;
381
+ });
382
+ };
383
+ const reorderViewSections = async (tabId, sectionIds) => {
384
+ const numericTabId = parseViewId(tabId);
385
+ const nextSectionIds = sectionIds.map(parseViewId);
386
+ return models.$transaction(async (tx) => {
387
+ const sections = await tx.viewSection.findMany({
388
+ where: { tabId: numericTabId },
389
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
390
+ select: { id: true }
391
+ });
392
+ const currentSectionIds = sections.map((section) => section.id);
393
+ ensureValidReorderIds(currentSectionIds, nextSectionIds);
394
+ await Promise.all(
395
+ nextSectionIds.map(
396
+ (sectionId, index) => tx.viewSection.update({
397
+ where: { id: sectionId },
398
+ data: { order: index }
399
+ })
400
+ )
401
+ );
402
+ const reorderedSections = await tx.viewSection.findMany({
403
+ where: { tabId: numericTabId },
404
+ orderBy: [{ order: "asc" }, { createdAt: "asc" }],
405
+ include: orderedViewSectionTagsInclude
406
+ });
407
+ return reorderedSections.map(serializeViewSection);
408
+ });
409
+ };
410
+ export {
411
+ DEFAULT_VIEW_SECTION_LIMIT,
412
+ MAX_VIEW_SECTION_LIMIT,
413
+ MIN_VIEW_SECTION_LIMIT,
414
+ clampViewSectionLimit,
415
+ createViewSection,
416
+ createViewTab,
417
+ deleteViewSection,
418
+ deleteViewTab,
419
+ getViewSectionById,
420
+ getViewSectionNotes,
421
+ getViewWorkspace,
422
+ normalizeViewSectionInput,
423
+ normalizeViewTabTitle,
424
+ normalizeViewTagNames,
425
+ pickNextActiveViewTabId,
426
+ reorderViewSections,
427
+ reorderViewTabs,
428
+ setActiveViewTab,
429
+ updateViewSection,
430
+ updateViewTab
431
+ };
432
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/view/services/workspace.ts"],"sourcesContent":["import type { Note, Prisma } from '@prisma/client';\nimport { buildNoteTagNamesWhere, type NoteTagMatchMode } from '~/features/note/services/tag-filter.js';\nimport models from '~/models.js';\n\nexport type ViewTagMatchMode = NoteTagMatchMode;\n\nexport interface ViewSectionRecord {\n id: string;\n tabId: string;\n title: string;\n tagNames: string[];\n mode: ViewTagMatchMode;\n limit: number;\n order: number;\n}\n\nexport interface ViewTabRecord {\n id: string;\n title: string;\n order: number;\n sections: ViewSectionRecord[];\n}\n\nexport interface ViewWorkspaceRecord {\n activeTabId: string | null;\n tabs: ViewTabRecord[];\n}\n\nexport interface ViewSectionInput {\n title?: string;\n tagNames: string[];\n mode?: ViewTagMatchMode;\n limit?: number;\n}\n\nexport interface ViewSectionNotesResult {\n totalCount: number;\n notes: Note[];\n}\n\nconst VIEW_WORKSPACE_ID = 1;\n\nexport const DEFAULT_VIEW_SECTION_LIMIT = 5;\nexport const MIN_VIEW_SECTION_LIMIT = 1;\nexport const MAX_VIEW_SECTION_LIMIT = 20;\n\ntype ViewDbClient = typeof models | Prisma.TransactionClient;\n\nconst orderedViewSectionTagsInclude = {\n tags: {\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n },\n} satisfies Prisma.ViewSectionInclude;\n\nconst orderedViewTabSectionsInclude = {\n sections: {\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n include: orderedViewSectionTagsInclude,\n },\n} satisfies Prisma.ViewTabInclude;\n\ntype DbViewSection = Prisma.ViewSectionGetPayload<{\n include: typeof orderedViewSectionTagsInclude;\n}>;\n\ntype DbViewTab = Prisma.ViewTabGetPayload<{\n include: typeof orderedViewTabSectionsInclude;\n}>;\n\nconst normalizeTagName = (value: string) => {\n const trimmedValue = value.trim();\n\n if (!trimmedValue) {\n return '';\n }\n\n if (trimmedValue.startsWith('@')) {\n return trimmedValue;\n }\n\n if (trimmedValue.startsWith('#')) {\n return `@${trimmedValue.slice(1)}`;\n }\n\n return `@${trimmedValue}`;\n};\n\nexport const normalizeViewTagNames = (values: string[]) => {\n const normalizedTagNames = values.flatMap((value) => value.split(','));\n\n return Array.from(new Set(normalizedTagNames.map(normalizeTagName).filter(Boolean)));\n};\n\nexport const clampViewSectionLimit = (value: number | undefined) => {\n const numericValue = typeof value === 'number' ? value : Number.NaN;\n\n if (!Number.isInteger(numericValue)) {\n return DEFAULT_VIEW_SECTION_LIMIT;\n }\n\n return Math.min(MAX_VIEW_SECTION_LIMIT, Math.max(MIN_VIEW_SECTION_LIMIT, numericValue));\n};\n\nexport const normalizeViewTabTitle = (title: string) => {\n const trimmedTitle = title.trim();\n\n return trimmedTitle || 'Untitled View';\n};\n\nconst buildDefaultSectionTitle = (tagNames: string[]) => {\n if (tagNames.length === 0) {\n return 'Tagged notes';\n }\n\n return tagNames.slice(0, 2).join(' + ');\n};\n\nexport const normalizeViewSectionInput = (input: ViewSectionInput) => {\n const tagNames = normalizeViewTagNames(input.tagNames);\n\n if (tagNames.length === 0) {\n throw new Error('A view section requires at least one tag.');\n }\n\n const trimmedTitle = input.title?.trim() ?? '';\n\n return {\n title: trimmedTitle || buildDefaultSectionTitle(tagNames),\n tagNames,\n mode: input.mode === 'or' ? 'or' : 'and',\n limit: clampViewSectionLimit(input.limit),\n } satisfies {\n title: string;\n tagNames: string[];\n mode: ViewTagMatchMode;\n limit: number;\n };\n};\n\nexport const pickNextActiveViewTabId = (tabIds: number[], deletedTabId: number, currentActiveTabId: number | null) => {\n const remainingTabIds = tabIds.filter((tabId) => tabId !== deletedTabId);\n\n if (remainingTabIds.length === 0) {\n return null;\n }\n\n if (currentActiveTabId !== deletedTabId) {\n return currentActiveTabId;\n }\n\n return remainingTabIds[0] ?? null;\n};\n\nconst ensureValidReorderIds = (currentIds: number[], nextIds: number[]) => {\n if (currentIds.length !== nextIds.length) {\n throw new Error('Reorder payload must include every item exactly once.');\n }\n\n const currentIdSet = new Set(currentIds);\n const nextIdSet = new Set(nextIds);\n\n if (currentIdSet.size !== currentIds.length || nextIdSet.size !== nextIds.length) {\n throw new Error('Reorder payload contains duplicate ids.');\n }\n\n for (const id of currentIdSet) {\n if (!nextIdSet.has(id)) {\n throw new Error('Reorder payload must match the current item set.');\n }\n }\n};\n\nconst parseViewId = (id: string) => {\n const numericId = Number(id);\n\n if (!Number.isInteger(numericId) || numericId <= 0) {\n throw new Error('A valid id is required.');\n }\n\n return numericId;\n};\n\nconst serializeViewSection = (section: DbViewSection): ViewSectionRecord => ({\n id: String(section.id),\n tabId: String(section.tabId),\n title: section.title,\n tagNames: section.tags.map((tag) => tag.name),\n mode: section.mode as ViewTagMatchMode,\n limit: section.limit,\n order: section.order,\n});\n\nconst serializeViewTab = (tab: DbViewTab): ViewTabRecord => ({\n id: String(tab.id),\n title: tab.title,\n order: tab.order,\n sections: tab.sections.map(serializeViewSection),\n});\n\nconst getOrderedViewTabs = async (db: ViewDbClient) => {\n return db.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n include: orderedViewTabSectionsInclude,\n });\n};\n\nconst ensureViewWorkspace = async (db: ViewDbClient) => {\n return db.viewWorkspace.upsert({\n where: { id: VIEW_WORKSPACE_ID },\n update: {},\n create: { id: VIEW_WORKSPACE_ID },\n });\n};\n\nconst readViewWorkspace = async (db: ViewDbClient): Promise<ViewWorkspaceRecord> => {\n const [workspace, tabs] = await Promise.all([ensureViewWorkspace(db), getOrderedViewTabs(db)]);\n\n const activeTabId =\n workspace.activeTabId !== null && tabs.some((tab) => tab.id === workspace.activeTabId)\n ? String(workspace.activeTabId)\n : tabs[0]\n ? String(tabs[0].id)\n : null;\n\n return {\n activeTabId,\n tabs: tabs.map(serializeViewTab),\n };\n};\n\nconst readViewSection = async (db: ViewDbClient, id: number): Promise<ViewSectionRecord | null> => {\n const section = await db.viewSection.findUnique({\n where: { id },\n include: orderedViewSectionTagsInclude,\n });\n\n return section ? serializeViewSection(section) : null;\n};\n\nconst getNextTabOrder = async (db: ViewDbClient) => {\n const lastTab = await db.viewTab.findFirst({\n orderBy: [{ order: 'desc' }, { createdAt: 'desc' }],\n select: { order: true },\n });\n\n return lastTab ? lastTab.order + 1 : 0;\n};\n\nconst getNextSectionOrder = async (db: ViewDbClient, tabId: number) => {\n const lastSection = await db.viewSection.findFirst({\n where: { tabId },\n orderBy: [{ order: 'desc' }, { createdAt: 'desc' }],\n select: { order: true },\n });\n\n return lastSection ? lastSection.order + 1 : 0;\n};\n\nconst resequenceViewTabs = async (db: ViewDbClient) => {\n const tabs = await db.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n\n await Promise.all(\n tabs.map((tab, index) =>\n db.viewTab.update({\n where: { id: tab.id },\n data: { order: index },\n }),\n ),\n );\n};\n\nconst resequenceViewSections = async (db: ViewDbClient, tabId: number) => {\n const sections = await db.viewSection.findMany({\n where: { tabId },\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n\n await Promise.all(\n sections.map((section, index) =>\n db.viewSection.update({\n where: { id: section.id },\n data: { order: index },\n }),\n ),\n );\n};\n\nexport const getViewWorkspace = async () => {\n return readViewWorkspace(models);\n};\n\nexport const getViewSectionById = async (id: string) => {\n return readViewSection(models, parseViewId(id));\n};\n\nexport const getViewSectionNotes = async (\n id: string,\n pagination: {\n limit: number;\n offset: number;\n },\n): Promise<ViewSectionNotesResult | null> => {\n const section = await models.viewSection.findUnique({\n where: { id: parseViewId(id) },\n include: orderedViewSectionTagsInclude,\n });\n\n if (!section) {\n return null;\n }\n\n const tagNames = section.tags.map((tag) => tag.name);\n\n if (tagNames.length === 0) {\n return {\n totalCount: 0,\n notes: [],\n };\n }\n\n const where = buildNoteTagNamesWhere(tagNames, section.mode as NoteTagMatchMode);\n\n const [totalCount, notes] = await Promise.all([\n models.note.count({ where }),\n models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n};\n\nexport const createViewTab = async (title: string) => {\n return models.$transaction(async (tx) => {\n const workspace = await ensureViewWorkspace(tx);\n const nextTabOrder = await getNextTabOrder(tx);\n const createdTab = await tx.viewTab.create({\n data: {\n title: normalizeViewTabTitle(title),\n order: nextTabOrder,\n },\n include: orderedViewTabSectionsInclude,\n });\n\n if (workspace.activeTabId === null) {\n await tx.viewWorkspace.update({\n where: { id: VIEW_WORKSPACE_ID },\n data: { activeTabId: createdTab.id },\n });\n }\n\n return serializeViewTab(createdTab);\n });\n};\n\nexport const updateViewTab = async (id: string, title: string) => {\n const updatedTab = await models.viewTab.update({\n where: { id: parseViewId(id) },\n data: {\n title: normalizeViewTabTitle(title),\n },\n include: orderedViewTabSectionsInclude,\n });\n\n return serializeViewTab(updatedTab);\n};\n\nexport const deleteViewTab = async (id: string) => {\n const tabId = parseViewId(id);\n\n return models.$transaction(async (tx) => {\n const workspace = await ensureViewWorkspace(tx);\n const tabs = await tx.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n\n if (!tabs.some((tab) => tab.id === tabId)) {\n return false;\n }\n\n await tx.viewTab.delete({ where: { id: tabId } });\n await resequenceViewTabs(tx);\n\n const nextActiveTabId = pickNextActiveViewTabId(\n tabs.map((tab) => tab.id),\n tabId,\n workspace.activeTabId,\n );\n\n await tx.viewWorkspace.update({\n where: { id: VIEW_WORKSPACE_ID },\n data: { activeTabId: nextActiveTabId },\n });\n\n return true;\n });\n};\n\nexport const setActiveViewTab = async (id: string) => {\n const tabId = parseViewId(id);\n\n return models.$transaction(async (tx) => {\n const workspace = await ensureViewWorkspace(tx);\n const tab = await tx.viewTab.findUnique({\n where: { id: tabId },\n select: { id: true },\n });\n\n if (!tab) {\n throw new Error('View tab not found.');\n }\n\n await tx.viewWorkspace.update({\n where: { id: workspace.id },\n data: { activeTabId: tabId },\n });\n\n return readViewWorkspace(tx);\n });\n};\n\nexport const reorderViewTabs = async (tabIds: string[]) => {\n const nextTabIds = tabIds.map(parseViewId);\n\n return models.$transaction(async (tx) => {\n const tabs = await tx.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n const currentTabIds = tabs.map((tab) => tab.id);\n\n ensureValidReorderIds(currentTabIds, nextTabIds);\n\n await Promise.all(\n nextTabIds.map((tabId, index) =>\n tx.viewTab.update({\n where: { id: tabId },\n data: { order: index },\n }),\n ),\n );\n\n const reorderedTabs = await getOrderedViewTabs(tx);\n return reorderedTabs.map(serializeViewTab);\n });\n};\n\nexport const createViewSection = async (tabId: string, input: ViewSectionInput) => {\n const numericTabId = parseViewId(tabId);\n const nextSection = normalizeViewSectionInput(input);\n\n return models.$transaction(async (tx) => {\n const tab = await tx.viewTab.findUnique({\n where: { id: numericTabId },\n select: { id: true },\n });\n\n if (!tab) {\n throw new Error('View tab not found.');\n }\n\n const nextSectionOrder = await getNextSectionOrder(tx, numericTabId);\n\n const createdSection = await tx.viewSection.create({\n data: {\n tabId: numericTabId,\n title: nextSection.title,\n mode: nextSection.mode,\n limit: nextSection.limit,\n order: nextSectionOrder,\n tags: {\n create: nextSection.tagNames.map((tagName, index) => ({\n name: tagName,\n order: index,\n })),\n },\n },\n include: orderedViewSectionTagsInclude,\n });\n\n return serializeViewSection(createdSection);\n });\n};\n\nexport const updateViewSection = async (id: string, input: ViewSectionInput) => {\n const sectionId = parseViewId(id);\n const nextSection = normalizeViewSectionInput(input);\n\n const updatedSection = await models.viewSection.update({\n where: { id: sectionId },\n data: {\n title: nextSection.title,\n mode: nextSection.mode,\n limit: nextSection.limit,\n tags: {\n deleteMany: {},\n create: nextSection.tagNames.map((tagName, index) => ({\n name: tagName,\n order: index,\n })),\n },\n },\n include: orderedViewSectionTagsInclude,\n });\n\n return serializeViewSection(updatedSection);\n};\n\nexport const deleteViewSection = async (id: string) => {\n const sectionId = parseViewId(id);\n\n return models.$transaction(async (tx) => {\n const section = await tx.viewSection.findUnique({\n where: { id: sectionId },\n select: { id: true, tabId: true },\n });\n\n if (!section) {\n return false;\n }\n\n await tx.viewSection.delete({\n where: { id: sectionId },\n });\n await resequenceViewSections(tx, section.tabId);\n\n return true;\n });\n};\n\nexport const reorderViewSections = async (tabId: string, sectionIds: string[]) => {\n const numericTabId = parseViewId(tabId);\n const nextSectionIds = sectionIds.map(parseViewId);\n\n return models.$transaction(async (tx) => {\n const sections = await tx.viewSection.findMany({\n where: { tabId: numericTabId },\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n const currentSectionIds = sections.map((section) => section.id);\n\n ensureValidReorderIds(currentSectionIds, nextSectionIds);\n\n await Promise.all(\n nextSectionIds.map((sectionId, index) =>\n tx.viewSection.update({\n where: { id: sectionId },\n data: { order: index },\n }),\n ),\n );\n\n const reorderedSections = await tx.viewSection.findMany({\n where: { tabId: numericTabId },\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n include: orderedViewSectionTagsInclude,\n });\n\n return reorderedSections.map(serializeViewSection);\n });\n};\n"],"mappings":"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","names":[]}
@@ -1,5 +1,6 @@
1
1
  import { createApp } from "./app.js";
2
2
  import { logAuthConfig, resolveAuthConfig } from "./modules/auth-mode.js";
3
+ import { startDataMaintenanceScheduler } from "./modules/data-maintenance.js";
3
4
  const PORT = Number(process.env.PORT || 6683);
4
5
  const HOST = process.env.HOST || "0.0.0.0";
5
6
  try {
@@ -9,6 +10,19 @@ try {
9
10
  app.listen(PORT, HOST, () => {
10
11
  process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})
11
12
  `);
13
+ startDataMaintenanceScheduler({
14
+ onResults: (results) => {
15
+ for (const result of results) {
16
+ process.stdout.write(`[maintenance] Reconciled ${result.processedCount} rows for ${result.key}
17
+ `);
18
+ }
19
+ },
20
+ onError: (error) => {
21
+ const message = error instanceof Error ? error.message : "Unknown data maintenance error";
22
+ process.stderr.write(`[maintenance] Background run failed: ${message}
23
+ `);
24
+ }
25
+ });
12
26
  });
13
27
  } catch (error) {
14
28
  const message = error instanceof Error ? error.message : "Unknown auth configuration error";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { createApp } from './app.js';\nimport { logAuthConfig, resolveAuthConfig } from './modules/auth-mode.js';\n\nconst PORT = Number(process.env.PORT || 6683);\nconst HOST = process.env.HOST || '0.0.0.0';\n\ntry {\n const authConfig = resolveAuthConfig(process.env);\n\n logAuthConfig(authConfig);\n\n const app = createApp(authConfig);\n\n app.listen(PORT, HOST, () => {\n process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})\\n`);\n });\n} catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown auth configuration error';\n process.stderr.write(`[auth] Startup failed: ${message}\\n`);\n process.exit(1);\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,eAAe,yBAAyB;AAEjD,MAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAC5C,MAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,IAAI;AACA,QAAM,aAAa,kBAAkB,QAAQ,GAAG;AAEhD,gBAAc,UAAU;AAExB,QAAM,MAAM,UAAU,UAAU;AAEhC,MAAI,OAAO,MAAM,MAAM,MAAM;AACzB,YAAQ,OAAO,MAAM,yBAAyB,IAAI,IAAI,IAAI,WAAW,WAAW,IAAI;AAAA,CAAK;AAAA,EAC7F,CAAC;AACL,SAAS,OAAO;AACZ,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,UAAQ,OAAO,MAAM,0BAA0B,OAAO;AAAA,CAAI;AAC1D,UAAQ,KAAK,CAAC;AAClB;","names":[]}
1
+ {"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { createApp } from './app.js';\nimport { logAuthConfig, resolveAuthConfig } from './modules/auth-mode.js';\nimport { startDataMaintenanceScheduler } from './modules/data-maintenance.js';\n\nconst PORT = Number(process.env.PORT || 6683);\nconst HOST = process.env.HOST || '0.0.0.0';\n\ntry {\n const authConfig = resolveAuthConfig(process.env);\n\n logAuthConfig(authConfig);\n\n const app = createApp(authConfig);\n\n app.listen(PORT, HOST, () => {\n process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})\\n`);\n\n startDataMaintenanceScheduler({\n onResults: (results) => {\n for (const result of results) {\n process.stdout.write(`[maintenance] Reconciled ${result.processedCount} rows for ${result.key}\\n`);\n }\n },\n onError: (error) => {\n const message = error instanceof Error ? error.message : 'Unknown data maintenance error';\n process.stderr.write(`[maintenance] Background run failed: ${message}\\n`);\n },\n });\n });\n} catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown auth configuration error';\n process.stderr.write(`[auth] Startup failed: ${message}\\n`);\n process.exit(1);\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,eAAe,yBAAyB;AACjD,SAAS,qCAAqC;AAE9C,MAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAC5C,MAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,IAAI;AACA,QAAM,aAAa,kBAAkB,QAAQ,GAAG;AAEhD,gBAAc,UAAU;AAExB,QAAM,MAAM,UAAU,UAAU;AAEhC,MAAI,OAAO,MAAM,MAAM,MAAM;AACzB,YAAQ,OAAO,MAAM,yBAAyB,IAAI,IAAI,IAAI,WAAW,WAAW,IAAI;AAAA,CAAK;AAEzF,kCAA8B;AAAA,MAC1B,WAAW,CAAC,YAAY;AACpB,mBAAW,UAAU,SAAS;AAC1B,kBAAQ,OAAO,MAAM,4BAA4B,OAAO,cAAc,aAAa,OAAO,GAAG;AAAA,CAAI;AAAA,QACrG;AAAA,MACJ;AAAA,MACA,SAAS,CAAC,UAAU;AAChB,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,gBAAQ,OAAO,MAAM,wCAAwC,OAAO;AAAA,CAAI;AAAA,MAC5E;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AACL,SAAS,OAAO;AACZ,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,UAAQ,OAAO,MAAM,0BAA0B,OAAO;AAAA,CAAI;AAC1D,UAAQ,KAAK,CAAC;AAClB;","names":[]}
@@ -6,10 +6,12 @@ const buildUnauthorizedPayload = () => ({
6
6
  message: "Authentication required"
7
7
  });
8
8
  const buildUnauthorizedGraphqlPayload = () => ({
9
- errors: [{
10
- message: "Authentication required",
11
- extensions: { code: "UNAUTHORIZED" }
12
- }]
9
+ errors: [
10
+ {
11
+ message: "Authentication required",
12
+ extensions: { code: "UNAUTHORIZED" }
13
+ }
14
+ ]
13
15
  });
14
16
  const isAuthenticatedRequest = (req) => Boolean(req.session?.authenticated);
15
17
  const createSessionMiddleware = (authConfig) => {
@@ -52,13 +54,12 @@ const createMutationAuthValidationRule = () => {
52
54
  if (node.operation !== "mutation") {
53
55
  return;
54
56
  }
55
- context.reportError(new GraphQLError(
56
- "Authentication required",
57
- {
57
+ context.reportError(
58
+ new GraphQLError("Authentication required", {
58
59
  nodes: [node],
59
60
  extensions: { code: "UNAUTHORIZED" }
60
- }
61
- ));
61
+ })
62
+ );
62
63
  }
63
64
  };
64
65
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/auth-guard.ts"],"sourcesContent":["import session from 'express-session';\nimport 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\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst buildUnauthorizedPayload = () => ({\n code: 'UNAUTHORIZED',\n message: 'Authentication required'\n});\n\nconst buildUnauthorizedGraphqlPayload = () => ({\n errors: [{\n message: 'Authentication required',\n extensions: { code: 'UNAUTHORIZED' }\n }]\n});\n\nexport const isAuthenticatedRequest = (req: Request) => Boolean(req.session?.authenticated);\n\nexport const createSessionMiddleware = (authConfig: AuthConfig): RequestHandler => {\n if (authConfig.mode !== 'password' || !authConfig.sessionSecret) {\n return (_req, _res, next) => next();\n }\n\n return session({\n secret: authConfig.sessionSecret,\n name: 'ocean-brain.sid',\n resave: false,\n saveUninitialized: false,\n cookie: {\n httpOnly: true,\n sameSite: 'lax'\n }\n });\n};\n\nexport const requireSessionForWrite = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'disabled' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res\n .status(401)\n .set(JSON_HEADERS)\n .json(buildUnauthorizedPayload())\n .end();\n };\n};\n\nexport const requireSessionForGraphql = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'disabled' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res\n .status(401)\n .set(JSON_HEADERS)\n .json(buildUnauthorizedGraphqlPayload())\n .end();\n };\n};\n\nexport const createMutationAuthValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation !== 'mutation') {\n return;\n }\n\n context.reportError(new GraphQLError(\n 'Authentication required',\n {\n nodes: [node],\n extensions: { code: 'UNAUTHORIZED' }\n }\n ));\n }\n };\n };\n};\n"],"mappings":"AAAA,OAAO,aAAa;AAGpB,SAAS,oBAAoB;AAI7B,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AAE1D,MAAM,2BAA2B,OAAO;AAAA,EACpC,MAAM;AAAA,EACN,SAAS;AACb;AAEA,MAAM,kCAAkC,OAAO;AAAA,EAC3C,QAAQ,CAAC;AAAA,IACL,SAAS;AAAA,IACT,YAAY,EAAE,MAAM,eAAe;AAAA,EACvC,CAAC;AACL;AAEO,MAAM,yBAAyB,CAAC,QAAiB,QAAQ,IAAI,SAAS,aAAa;AAEnF,MAAM,0BAA0B,CAAC,eAA2C;AAC/E,MAAI,WAAW,SAAS,cAAc,CAAC,WAAW,eAAe;AAC7D,WAAO,CAAC,MAAM,MAAM,SAAS,KAAK;AAAA,EACtC;AAEA,SAAO,QAAQ;AAAA,IACX,QAAQ,WAAW;AAAA,IACnB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB,QAAQ;AAAA,MACJ,UAAU;AAAA,MACV,UAAU;AAAA,IACd;AAAA,EACJ,CAAC;AACL;AAEO,MAAM,yBAAyB,CAAC,eAA2C;AAC9E,SAAO,CAAC,KAAc,KAAe,SAAuB;AACxD,QAAI,WAAW,SAAS,cAAc,uBAAuB,GAAG,GAAG;AAC/D,WAAK;AACL;AAAA,IACJ;AAEA,QACK,OAAO,GAAG,EACV,IAAI,YAAY,EAChB,KAAK,yBAAyB,CAAC,EAC/B,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,2BAA2B,CAAC,eAA2C;AAChF,SAAO,CAAC,KAAc,KAAe,SAAuB;AACxD,QAAI,WAAW,SAAS,cAAc,uBAAuB,GAAG,GAAG;AAC/D,WAAK;AACL;AAAA,IACJ;AAEA,QACK,OAAO,GAAG,EACV,IAAI,YAAY,EAChB,KAAK,gCAAgC,CAAC,EACtC,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,mCAAmC,MAAsB;AAClE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,YAAY;AAC/B;AAAA,QACJ;AAEA,gBAAQ,YAAY,IAAI;AAAA,UACpB;AAAA,UACA;AAAA,YACI,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY,EAAE,MAAM,eAAe;AAAA,UACvC;AAAA,QACJ,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/auth-guard.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport session from 'express-session';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst buildUnauthorizedPayload = () => ({\n code: 'UNAUTHORIZED',\n message: 'Authentication required',\n});\n\nconst buildUnauthorizedGraphqlPayload = () => ({\n errors: [\n {\n message: 'Authentication required',\n extensions: { code: 'UNAUTHORIZED' },\n },\n ],\n});\n\nexport const isAuthenticatedRequest = (req: Request) => Boolean(req.session?.authenticated);\n\nexport const createSessionMiddleware = (authConfig: AuthConfig): RequestHandler => {\n if (authConfig.mode !== 'password' || !authConfig.sessionSecret) {\n return (_req, _res, next) => next();\n }\n\n return session({\n secret: authConfig.sessionSecret,\n name: 'ocean-brain.sid',\n resave: false,\n saveUninitialized: false,\n cookie: {\n httpOnly: true,\n sameSite: 'lax',\n },\n });\n};\n\nexport const requireSessionForWrite = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'disabled' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.status(401).set(JSON_HEADERS).json(buildUnauthorizedPayload()).end();\n };\n};\n\nexport const requireSessionForGraphql = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'disabled' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.status(401).set(JSON_HEADERS).json(buildUnauthorizedGraphqlPayload()).end();\n };\n};\n\nexport const createMutationAuthValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation !== 'mutation') {\n return;\n }\n\n context.reportError(\n new GraphQLError('Authentication required', {\n nodes: [node],\n extensions: { code: 'UNAUTHORIZED' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AACA,OAAO,aAAa;AAEpB,SAAS,oBAAoB;AAI7B,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AAE1D,MAAM,2BAA2B,OAAO;AAAA,EACpC,MAAM;AAAA,EACN,SAAS;AACb;AAEA,MAAM,kCAAkC,OAAO;AAAA,EAC3C,QAAQ;AAAA,IACJ;AAAA,MACI,SAAS;AAAA,MACT,YAAY,EAAE,MAAM,eAAe;AAAA,IACvC;AAAA,EACJ;AACJ;AAEO,MAAM,yBAAyB,CAAC,QAAiB,QAAQ,IAAI,SAAS,aAAa;AAEnF,MAAM,0BAA0B,CAAC,eAA2C;AAC/E,MAAI,WAAW,SAAS,cAAc,CAAC,WAAW,eAAe;AAC7D,WAAO,CAAC,MAAM,MAAM,SAAS,KAAK;AAAA,EACtC;AAEA,SAAO,QAAQ;AAAA,IACX,QAAQ,WAAW;AAAA,IACnB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB,QAAQ;AAAA,MACJ,UAAU;AAAA,MACV,UAAU;AAAA,IACd;AAAA,EACJ,CAAC;AACL;AAEO,MAAM,yBAAyB,CAAC,eAA2C;AAC9E,SAAO,CAAC,KAAc,KAAe,SAAuB;AACxD,QAAI,WAAW,SAAS,cAAc,uBAAuB,GAAG,GAAG;AAC/D,WAAK;AACL;AAAA,IACJ;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI,YAAY,EAAE,KAAK,yBAAyB,CAAC,EAAE,IAAI;AAAA,EAC3E;AACJ;AAEO,MAAM,2BAA2B,CAAC,eAA2C;AAChF,SAAO,CAAC,KAAc,KAAe,SAAuB;AACxD,QAAI,WAAW,SAAS,cAAc,uBAAuB,GAAG,GAAG;AAC/D,WAAK;AACL;AAAA,IACJ;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI,YAAY,EAAE,KAAK,gCAAgC,CAAC,EAAE,IAAI;AAAA,EAClF;AACJ;AAEO,MAAM,mCAAmC,MAAsB;AAClE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,YAAY;AAC/B;AAAA,QACJ;AAEA,gBAAQ;AAAA,UACJ,IAAI,aAAa,2BAA2B;AAAA,YACxC,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY,EAAE,MAAM,eAAe;AAAA,UACvC,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
@@ -6,9 +6,7 @@ const isTruthy = (value) => {
6
6
  };
7
7
  const ensurePasswordModeRequirements = (env) => {
8
8
  if (!env.OCEAN_BRAIN_PASSWORD) {
9
- throw new Error(
10
- "Missing OCEAN_BRAIN_PASSWORD. Set OCEAN_BRAIN_PASSWORD to enable password auth mode."
11
- );
9
+ throw new Error("Missing OCEAN_BRAIN_PASSWORD. Set OCEAN_BRAIN_PASSWORD to enable password auth mode.");
12
10
  }
13
11
  if (!env.OCEAN_BRAIN_SESSION_SECRET) {
14
12
  throw new Error(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/auth-mode.ts"],"sourcesContent":["export type AuthMode = 'password' | 'disabled';\n\nexport interface AuthConfig {\n mode: AuthMode;\n password?: string;\n sessionSecret?: string;\n source: 'auto' | 'override';\n}\n\nexport interface AuthModeEnvironment {\n [key: string]: string | undefined;\n OCEAN_BRAIN_ALLOW_INSECURE_NO_AUTH?: string;\n OCEAN_BRAIN_PASSWORD?: string;\n OCEAN_BRAIN_SESSION_SECRET?: string;\n}\n\nconst isTruthy = (value?: string) => {\n if (!value) {\n return false;\n }\n\n return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());\n};\n\nconst ensurePasswordModeRequirements = (env: AuthModeEnvironment) => {\n if (!env.OCEAN_BRAIN_PASSWORD) {\n throw new Error(\n 'Missing OCEAN_BRAIN_PASSWORD. Set OCEAN_BRAIN_PASSWORD to enable password auth mode.'\n );\n }\n\n if (!env.OCEAN_BRAIN_SESSION_SECRET) {\n throw new Error(\n 'Missing OCEAN_BRAIN_SESSION_SECRET. Set OCEAN_BRAIN_SESSION_SECRET when password auth mode is enabled.'\n );\n }\n};\n\nexport const resolveAuthConfig = (env: AuthModeEnvironment): AuthConfig => {\n const allowInsecureNoAuth = isTruthy(env.OCEAN_BRAIN_ALLOW_INSECURE_NO_AUTH);\n\n if (allowInsecureNoAuth) {\n return {\n mode: 'disabled',\n source: 'auto'\n };\n }\n\n if (env.OCEAN_BRAIN_PASSWORD) {\n ensurePasswordModeRequirements(env);\n\n return {\n mode: 'password',\n password: env.OCEAN_BRAIN_PASSWORD,\n sessionSecret: env.OCEAN_BRAIN_SESSION_SECRET,\n source: 'auto'\n };\n }\n\n throw new Error(\n 'Unable to resolve auth mode. Set OCEAN_BRAIN_PASSWORD and OCEAN_BRAIN_SESSION_SECRET for password mode, or set OCEAN_BRAIN_ALLOW_INSECURE_NO_AUTH=true for disabled mode.'\n );\n};\n\nexport const logAuthConfig = (authConfig: AuthConfig) => {\n if (authConfig.mode === 'disabled') {\n process.stderr.write('[auth] Running in disabled mode. Authentication is not required for write access.\\n');\n return;\n }\n\n process.stdout.write('[auth] Running in password mode. Write access requires an authenticated session.\\n');\n};\n"],"mappings":"AAgBA,MAAM,WAAW,CAAC,UAAmB;AACjC,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,SAAO,CAAC,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,MAAM,KAAK,EAAE,YAAY,CAAC;AACzE;AAEA,MAAM,iCAAiC,CAAC,QAA6B;AACjE,MAAI,CAAC,IAAI,sBAAsB;AAC3B,UAAM,IAAI;AAAA,MACN;AAAA,IACJ;AAAA,EACJ;AAEA,MAAI,CAAC,IAAI,4BAA4B;AACjC,UAAM,IAAI;AAAA,MACN;AAAA,IACJ;AAAA,EACJ;AACJ;AAEO,MAAM,oBAAoB,CAAC,QAAyC;AACvE,QAAM,sBAAsB,SAAS,IAAI,kCAAkC;AAE3E,MAAI,qBAAqB;AACrB,WAAO;AAAA,MACH,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ;AAAA,EACJ;AAEA,MAAI,IAAI,sBAAsB;AAC1B,mCAA+B,GAAG;AAElC,WAAO;AAAA,MACH,MAAM;AAAA,MACN,UAAU,IAAI;AAAA,MACd,eAAe,IAAI;AAAA,MACnB,QAAQ;AAAA,IACZ;AAAA,EACJ;AAEA,QAAM,IAAI;AAAA,IACN;AAAA,EACJ;AACJ;AAEO,MAAM,gBAAgB,CAAC,eAA2B;AACrD,MAAI,WAAW,SAAS,YAAY;AAChC,YAAQ,OAAO,MAAM,qFAAqF;AAC1G;AAAA,EACJ;AAEA,UAAQ,OAAO,MAAM,oFAAoF;AAC7G;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/auth-mode.ts"],"sourcesContent":["export type AuthMode = 'password' | 'disabled';\n\nexport interface AuthConfig {\n mode: AuthMode;\n password?: string;\n sessionSecret?: string;\n source: 'auto' | 'override';\n}\n\nexport interface AuthModeEnvironment {\n [key: string]: string | undefined;\n OCEAN_BRAIN_ALLOW_INSECURE_NO_AUTH?: string;\n OCEAN_BRAIN_PASSWORD?: string;\n OCEAN_BRAIN_SESSION_SECRET?: string;\n}\n\nconst isTruthy = (value?: string) => {\n if (!value) {\n return false;\n }\n\n return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());\n};\n\nconst ensurePasswordModeRequirements = (env: AuthModeEnvironment) => {\n if (!env.OCEAN_BRAIN_PASSWORD) {\n throw new Error('Missing OCEAN_BRAIN_PASSWORD. Set OCEAN_BRAIN_PASSWORD to enable password auth mode.');\n }\n\n if (!env.OCEAN_BRAIN_SESSION_SECRET) {\n throw new Error(\n 'Missing OCEAN_BRAIN_SESSION_SECRET. Set OCEAN_BRAIN_SESSION_SECRET when password auth mode is enabled.',\n );\n }\n};\n\nexport const resolveAuthConfig = (env: AuthModeEnvironment): AuthConfig => {\n const allowInsecureNoAuth = isTruthy(env.OCEAN_BRAIN_ALLOW_INSECURE_NO_AUTH);\n\n if (allowInsecureNoAuth) {\n return {\n mode: 'disabled',\n source: 'auto',\n };\n }\n\n if (env.OCEAN_BRAIN_PASSWORD) {\n ensurePasswordModeRequirements(env);\n\n return {\n mode: 'password',\n password: env.OCEAN_BRAIN_PASSWORD,\n sessionSecret: env.OCEAN_BRAIN_SESSION_SECRET,\n source: 'auto',\n };\n }\n\n throw new Error(\n 'Unable to resolve auth mode. Set OCEAN_BRAIN_PASSWORD and OCEAN_BRAIN_SESSION_SECRET for password mode, or set OCEAN_BRAIN_ALLOW_INSECURE_NO_AUTH=true for disabled mode.',\n );\n};\n\nexport const logAuthConfig = (authConfig: AuthConfig) => {\n if (authConfig.mode === 'disabled') {\n process.stderr.write('[auth] Running in disabled mode. Authentication is not required for write access.\\n');\n return;\n }\n\n process.stdout.write('[auth] Running in password mode. Write access requires an authenticated session.\\n');\n};\n"],"mappings":"AAgBA,MAAM,WAAW,CAAC,UAAmB;AACjC,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,SAAO,CAAC,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,MAAM,KAAK,EAAE,YAAY,CAAC;AACzE;AAEA,MAAM,iCAAiC,CAAC,QAA6B;AACjE,MAAI,CAAC,IAAI,sBAAsB;AAC3B,UAAM,IAAI,MAAM,sFAAsF;AAAA,EAC1G;AAEA,MAAI,CAAC,IAAI,4BAA4B;AACjC,UAAM,IAAI;AAAA,MACN;AAAA,IACJ;AAAA,EACJ;AACJ;AAEO,MAAM,oBAAoB,CAAC,QAAyC;AACvE,QAAM,sBAAsB,SAAS,IAAI,kCAAkC;AAE3E,MAAI,qBAAqB;AACrB,WAAO;AAAA,MACH,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ;AAAA,EACJ;AAEA,MAAI,IAAI,sBAAsB;AAC1B,mCAA+B,GAAG;AAElC,WAAO;AAAA,MACH,MAAM;AAAA,MACN,UAAU,IAAI;AAAA,MACd,eAAe,IAAI;AAAA,MACnB,QAAQ;AAAA,IACZ;AAAA,EACJ;AAEA,QAAM,IAAI;AAAA,IACN;AAAA,EACJ;AACJ;AAEO,MAAM,gBAAgB,CAAC,eAA2B;AACrD,MAAI,WAAW,SAAS,YAAY;AAChC,YAAQ,OAAO,MAAM,qFAAqF;AAC1G;AAAA,EACJ;AAEA,UAAQ,OAAO,MAAM,oFAAoF;AAC7G;","names":[]}