@ecsplico/editor 1.0.0 → 2.1.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 (363) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +7 -7
  3. package/dist/adapters/anchors.d.ts +80 -0
  4. package/dist/adapters/anchors.js +270 -0
  5. package/dist/adapters/anchors.js.map +1 -0
  6. package/dist/adapters/comments/client.d.ts +64 -0
  7. package/dist/adapters/comments/client.js +536 -0
  8. package/dist/adapters/comments/client.js.map +1 -0
  9. package/dist/adapters/comments/drizzle.d.ts +430 -0
  10. package/dist/adapters/comments/drizzle.js +236 -0
  11. package/dist/adapters/comments/drizzle.js.map +1 -0
  12. package/dist/adapters/comments/errors.d.ts +9 -0
  13. package/dist/adapters/comments/errors.js +19 -0
  14. package/dist/adapters/comments/errors.js.map +1 -0
  15. package/dist/adapters/comments/json.d.ts +19 -0
  16. package/dist/adapters/comments/json.js +94 -0
  17. package/dist/adapters/comments/json.js.map +1 -0
  18. package/dist/adapters/comments/record.js +78 -0
  19. package/dist/adapters/comments/record.js.map +1 -0
  20. package/dist/adapters/comments/server.d.ts +23 -0
  21. package/dist/adapters/comments/server.js +102 -0
  22. package/dist/adapters/comments/server.js.map +1 -0
  23. package/dist/adapters/comments/types.d.ts +109 -0
  24. package/dist/adapters/documents.js +108 -0
  25. package/dist/adapters/documents.js.map +1 -0
  26. package/dist/adapters/drizzle.d.ts +293 -0
  27. package/dist/adapters/drizzle.js +157 -0
  28. package/dist/adapters/drizzle.js.map +1 -0
  29. package/dist/adapters/files.js +30 -0
  30. package/dist/adapters/files.js.map +1 -0
  31. package/dist/adapters/forge.d.ts +58 -0
  32. package/dist/adapters/forge.js +91 -0
  33. package/dist/adapters/forge.js.map +1 -0
  34. package/dist/adapters/index.d.ts +13 -0
  35. package/dist/adapters/index.js +0 -0
  36. package/dist/adapters/json.d.ts +24 -0
  37. package/dist/adapters/json.js +128 -0
  38. package/dist/adapters/json.js.map +1 -0
  39. package/dist/adapters/nitro.d.ts +61 -0
  40. package/dist/adapters/nitro.js +212 -0
  41. package/dist/adapters/nitro.js.map +1 -0
  42. package/dist/adapters/routes.js +16 -0
  43. package/dist/adapters/routes.js.map +1 -0
  44. package/dist/adapters/shared.d.ts +37 -0
  45. package/dist/adapters/shared.js +32 -0
  46. package/dist/adapters/shared.js.map +1 -0
  47. package/dist/adapters/vue.d.ts +67 -0
  48. package/dist/adapters/vue.js +187 -0
  49. package/dist/adapters/vue.js.map +1 -0
  50. package/dist/code/CodeSurface.vue.d.ts +3 -3
  51. package/dist/core/doc-anchors.d.ts +5 -0
  52. package/dist/core/doc-anchors.js +4 -3
  53. package/dist/core/doc-anchors.js.map +1 -1
  54. package/dist/core/editor-ui.js +57 -1
  55. package/dist/core/editor-ui.js.map +1 -1
  56. package/dist/core/notify.d.ts +1 -1
  57. package/dist/core/notify.js.map +1 -1
  58. package/dist/core/surface-session.js +139 -0
  59. package/dist/core/surface-session.js.map +1 -0
  60. package/dist/document/DocumentSurface.d.ts +1 -1
  61. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  62. package/dist/features/ai/agent-op-review.d.ts +2 -2
  63. package/dist/features/ai/agent-op-review.js.map +1 -1
  64. package/dist/features/ai/agent-ops.d.ts +1 -1
  65. package/dist/features/ai/agent-ops.js +9 -6
  66. package/dist/features/ai/agent-ops.js.map +1 -1
  67. package/dist/features/ai/ai-apply.d.ts +1 -1
  68. package/dist/features/ai/ai-review.js +10 -1
  69. package/dist/features/ai/ai-review.js.map +1 -1
  70. package/dist/features/ai/components/AiSelectionMenu.js +78 -252
  71. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  72. package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
  73. package/dist/features/ai/components/AiSidebar.js +2 -2
  74. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  75. package/dist/features/ai/components/DocumentChatOps.js +1 -0
  76. package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
  77. package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
  78. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  79. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  80. package/dist/features/ai/document-chat.d.ts +3 -2
  81. package/dist/features/ai/document-chat.js +16 -19
  82. package/dist/features/ai/document-chat.js.map +1 -1
  83. package/dist/features/ai/index.d.ts +5 -3
  84. package/dist/features/ai/index.js +4 -2
  85. package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
  86. package/dist/features/ai/protocol/agent-ops.js +65 -39
  87. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  88. package/dist/features/ai/protocol/block-lines.d.ts +89 -0
  89. package/dist/features/ai/protocol/block-lines.js +177 -0
  90. package/dist/features/ai/protocol/block-lines.js.map +1 -0
  91. package/dist/features/ai/protocol/chat.js +10 -10
  92. package/dist/features/ai/protocol/chat.js.map +1 -1
  93. package/dist/features/ai/protocol/index.d.ts +3 -2
  94. package/dist/features/ai/protocol/index.js +3 -2
  95. package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
  96. package/dist/features/ai/react/AiSelectionMenu.js +64 -255
  97. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  98. package/dist/features/ai/react/AiSidebar.js +2 -2
  99. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  100. package/dist/features/ai/react/DocumentChatOps.js +1 -0
  101. package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
  102. package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
  103. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  104. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  105. package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
  106. package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
  107. package/dist/features/ai/selection-flow.d.ts +133 -0
  108. package/dist/features/ai/selection-flow.js +283 -0
  109. package/dist/features/ai/selection-flow.js.map +1 -0
  110. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  111. package/dist/features/ai/use-document-chat.d.ts +1 -1
  112. package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
  113. package/dist/features/authors/feature.js +1 -1
  114. package/dist/features/authors/index.js +1 -1
  115. package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
  116. package/dist/features/comments/components/CommentBubble.js +1 -1
  117. package/dist/features/comments/components/CommentComposer.js +1 -1
  118. package/dist/features/comments/components/CommentThreadView.js +5 -2
  119. package/dist/features/comments/components/CommentThreadView.js.map +1 -1
  120. package/dist/features/comments/components/CommentsMargin.js +108 -230
  121. package/dist/features/comments/components/CommentsMargin.js.map +1 -1
  122. package/dist/features/comments/components/CommentsOverlay.js +1 -1
  123. package/dist/features/comments/feature.js +1 -1
  124. package/dist/features/comments/index.d.ts +4 -2
  125. package/dist/features/comments/index.js +4 -2
  126. package/dist/features/comments/margin-controller.d.ts +44 -0
  127. package/dist/features/comments/margin-controller.js +132 -0
  128. package/dist/features/comments/margin-controller.js.map +1 -0
  129. package/dist/features/comments/margin-layout.d.ts +55 -0
  130. package/dist/features/comments/margin-layout.js +79 -0
  131. package/dist/features/comments/margin-layout.js.map +1 -0
  132. package/dist/features/comments/model.d.ts +8 -0
  133. package/dist/features/comments/model.js +60 -34
  134. package/dist/features/comments/model.js.map +1 -1
  135. package/dist/features/comments/react/CommentBubble.js +1 -1
  136. package/dist/features/comments/react/CommentComposer.js +1 -1
  137. package/dist/features/comments/react/CommentThreadView.js +2 -2
  138. package/dist/features/comments/react/CommentThreadView.js.map +1 -1
  139. package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
  140. package/dist/features/comments/react/CommentsMargin.js +85 -251
  141. package/dist/features/comments/react/CommentsMargin.js.map +1 -1
  142. package/dist/features/comments/react/CommentsOverlay.js +1 -1
  143. package/dist/features/comments/react/use-comments.js +1 -1
  144. package/dist/features/comments/react/use-margin-layout.js +45 -0
  145. package/dist/features/comments/react/use-margin-layout.js.map +1 -0
  146. package/dist/features/comments/react.d.ts +1 -1
  147. package/dist/features/comments/schema.d.ts +23 -0
  148. package/dist/features/comments/schema.js.map +1 -1
  149. package/dist/features/comments/snapshot-equality.js +40 -0
  150. package/dist/features/comments/snapshot-equality.js.map +1 -0
  151. package/dist/features/comments/thread-anchor.js +33 -0
  152. package/dist/features/comments/thread-anchor.js.map +1 -0
  153. package/dist/features/comments/use-comments.d.ts +3 -1
  154. package/dist/features/comments/use-comments.js +1 -0
  155. package/dist/features/comments/use-comments.js.map +1 -1
  156. package/dist/features/comments/use-margin-layout.js +29 -0
  157. package/dist/features/comments/use-margin-layout.js.map +1 -0
  158. package/dist/features/comments/vue.d.ts +1 -1
  159. package/dist/features/composites/accepted-children.d.ts +21 -0
  160. package/dist/features/composites/accepted-children.js +126 -0
  161. package/dist/features/composites/accepted-children.js.map +1 -0
  162. package/dist/features/composites/components/CompositeDetails.js +1 -1
  163. package/dist/features/composites/components/CompositeInspector.js +38 -164
  164. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  165. package/dist/features/composites/feature.d.ts +2 -1
  166. package/dist/features/composites/feature.js +8 -2
  167. package/dist/features/composites/feature.js.map +1 -1
  168. package/dist/features/composites/index.d.ts +5 -3
  169. package/dist/features/composites/index.js +8 -6
  170. package/dist/features/composites/inspector-model.d.ts +97 -0
  171. package/dist/features/composites/inspector-model.js +255 -0
  172. package/dist/features/composites/inspector-model.js.map +1 -0
  173. package/dist/features/composites/kinds.d.ts +11 -0
  174. package/dist/features/composites/kinds.js +17 -2
  175. package/dist/features/composites/kinds.js.map +1 -1
  176. package/dist/features/composites/outline.d.ts +18 -5
  177. package/dist/features/composites/outline.js +44 -12
  178. package/dist/features/composites/outline.js.map +1 -1
  179. package/dist/features/composites/react/CompositeDetails.js +1 -1
  180. package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
  181. package/dist/features/composites/react/CompositeInspector.js +21 -180
  182. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  183. package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
  184. package/dist/features/composites/react/CompositeView.js +1 -1
  185. package/dist/features/composites/react/use-composite-inspector.js +29 -0
  186. package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
  187. package/dist/features/composites/schema.d.ts +2 -2
  188. package/dist/features/composites/schema.js +2 -2
  189. package/dist/features/composites/schema.js.map +1 -1
  190. package/dist/features/composites/use-composite-anchors.d.ts +1 -1
  191. package/dist/features/composites/use-composite-inspector.js +41 -0
  192. package/dist/features/composites/use-composite-inspector.js.map +1 -0
  193. package/dist/features/composites/use-composite-rows.d.ts +1 -1
  194. package/dist/features/media/index.d.ts +1 -1
  195. package/dist/features/media/vue.d.ts +1 -1
  196. package/dist/features/mentions/index.d.ts +3 -3
  197. package/dist/features/mentions/index.js +3 -3
  198. package/dist/features/mentions/schema.d.ts +1 -1
  199. package/dist/features/mentions/schema.js +1 -1
  200. package/dist/features/themes/index.d.ts +1 -1
  201. package/dist/features/themes/index.js +1 -1
  202. package/dist/features/transcript/feature.js +1 -1
  203. package/dist/features/transcript/index.js +2 -2
  204. package/dist/i18n/de/ai.js +2 -0
  205. package/dist/i18n/de/ai.js.map +1 -1
  206. package/dist/i18n/de/common.js +1 -2
  207. package/dist/i18n/de/common.js.map +1 -1
  208. package/dist/i18n/de/composites.js +8 -1
  209. package/dist/i18n/de/composites.js.map +1 -1
  210. package/dist/i18n/de/tree.js +14 -1
  211. package/dist/i18n/de/tree.js.map +1 -1
  212. package/dist/i18n/de.d.ts +22 -1
  213. package/dist/i18n/en/ai.js +2 -0
  214. package/dist/i18n/en/ai.js.map +1 -1
  215. package/dist/i18n/en/common.js +1 -2
  216. package/dist/i18n/en/common.js.map +1 -1
  217. package/dist/i18n/en/composites.js +8 -1
  218. package/dist/i18n/en/composites.js.map +1 -1
  219. package/dist/i18n/en/tree.js +14 -1
  220. package/dist/i18n/en/tree.js.map +1 -1
  221. package/dist/integrations/composite-ai/composite-fill.js +2 -2
  222. package/dist/integrations/composite-ai/composite-summary.js +2 -2
  223. package/dist/integrations/composite-ai/index.js +2 -2
  224. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  225. package/dist/markdown/MarkdownSurface.d.ts +1 -1
  226. package/dist/markdown/MarkdownSurface.js +2 -2
  227. package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
  228. package/dist/markdown/index.d.ts +2 -2
  229. package/dist/nuxt/module.d.ts +0 -6
  230. package/dist/nuxt/module.js +3 -5
  231. package/dist/nuxt/module.js.map +1 -1
  232. package/dist/nuxt/runtime/install-context.d.ts +2 -3
  233. package/dist/nuxt/runtime/install-context.js +2 -3
  234. package/dist/nuxt/runtime/install-context.js.map +1 -1
  235. package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +1 -1
  236. package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
  237. package/dist/react/EditorSurface.d.ts +1 -1
  238. package/dist/react/EditorSurface.js +43 -149
  239. package/dist/react/EditorSurface.js.map +1 -1
  240. package/dist/react/EditorToolbar.js +3 -40
  241. package/dist/react/EditorToolbar.js.map +1 -1
  242. package/dist/react/styles/ui-vars.css +2 -2
  243. package/dist/schema/advanced.js +2 -2
  244. package/dist/server/adapter.d.ts +2 -2
  245. package/dist/server/collab.js +1 -1
  246. package/dist/server/handlers/ai.d.ts +4 -2
  247. package/dist/server/handlers/ai.js +11 -9
  248. package/dist/server/handlers/ai.js.map +1 -1
  249. package/dist/server/handlers/context.d.ts +1 -1
  250. package/dist/server/handlers/editor.d.ts +1 -1
  251. package/dist/server/handlers/editor.js +2 -2
  252. package/dist/server/host.d.ts +1 -1
  253. package/dist/server/index.d.ts +2 -2
  254. package/dist/settings/index.d.ts +1 -1
  255. package/dist/styles/ai.css +1 -1
  256. package/dist/styles/authors.css +1 -1
  257. package/dist/styles/block-ui.css +1 -1
  258. package/dist/styles/collab.css +1 -1
  259. package/dist/styles/comments.css +1 -1
  260. package/dist/styles/core.css +2 -2
  261. package/dist/styles/media.css +1 -1
  262. package/dist/styles/mentions.css +1 -1
  263. package/dist/styles/themes.css +1 -1
  264. package/dist/styles/tokens.css +1 -1
  265. package/dist/styles/transcript.css +1 -1
  266. package/dist/tree/DocumentTree.js +159 -387
  267. package/dist/tree/DocumentTree.js.map +1 -1
  268. package/dist/tree/drop-rules.js +37 -0
  269. package/dist/tree/drop-rules.js.map +1 -0
  270. package/dist/tree/react/DocumentTree.d.ts +2 -2
  271. package/dist/tree/react/DocumentTree.js +135 -397
  272. package/dist/tree/react/DocumentTree.js.map +1 -1
  273. package/dist/tree/react/use-tree-model.js +129 -0
  274. package/dist/tree/react/use-tree-model.js.map +1 -0
  275. package/dist/tree/react.js +1 -1
  276. package/dist/tree/tree-host.js +100 -0
  277. package/dist/tree/tree-host.js.map +1 -0
  278. package/dist/tree/tree-items.js +22 -43
  279. package/dist/tree/tree-items.js.map +1 -1
  280. package/dist/tree/tree-model.js +580 -0
  281. package/dist/tree/tree-model.js.map +1 -0
  282. package/dist/tree/tree-rows.js +77 -0
  283. package/dist/tree/tree-rows.js.map +1 -0
  284. package/dist/tree/use-tree-model.js +94 -0
  285. package/dist/tree/use-tree-model.js.map +1 -0
  286. package/dist/tree/vue.js +1 -1
  287. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  288. package/dist/vue/SimpleEditor.vue.d.ts +2 -2
  289. package/dist/vue/components/EditorSurface.js +27 -87
  290. package/dist/vue/components/EditorSurface.js.map +1 -1
  291. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  292. package/dist/vue/components/EditorToolbar.js +7 -44
  293. package/dist/vue/components/EditorToolbar.js.map +1 -1
  294. package/dist/vue/context.d.ts +4 -4
  295. package/dist/vue/context.js +3 -2
  296. package/dist/vue/context.js.map +1 -1
  297. package/dist/vue/kit/types.d.ts +1 -1
  298. package/dist/vue/kit/use-ui-kit.d.ts +2 -2
  299. package/dist/vue/kit/use-ui-kit.js +3 -3
  300. package/dist/vue/kit/use-ui-kit.js.map +1 -1
  301. package/dist/vue/use-editor-ui.d.ts +1 -1
  302. package/dist/vue/use-notifier.d.ts +1 -1
  303. package/docs/integration.md +98 -5
  304. package/docs/llms.md +10 -2
  305. package/docs/usage.md +10 -5
  306. package/package.json +70 -23
  307. package/dist/nuxt/runtime/plugins/editor-context-primevue.client.ts +0 -8
  308. package/dist/vue/kit/primevue/Alert.js +0 -49
  309. package/dist/vue/kit/primevue/Alert.js.map +0 -1
  310. package/dist/vue/kit/primevue/Avatar.js +0 -54
  311. package/dist/vue/kit/primevue/Avatar.js.map +0 -1
  312. package/dist/vue/kit/primevue/Badge.js +0 -41
  313. package/dist/vue/kit/primevue/Badge.js.map +0 -1
  314. package/dist/vue/kit/primevue/Button.js +0 -112
  315. package/dist/vue/kit/primevue/Button.js.map +0 -1
  316. package/dist/vue/kit/primevue/Chip.js +0 -38
  317. package/dist/vue/kit/primevue/Chip.js.map +0 -1
  318. package/dist/vue/kit/primevue/DropdownMenu.js +0 -98
  319. package/dist/vue/kit/primevue/DropdownMenu.js.map +0 -1
  320. package/dist/vue/kit/primevue/FieldGroup.js +0 -22
  321. package/dist/vue/kit/primevue/FieldGroup.js.map +0 -1
  322. package/dist/vue/kit/primevue/FormField.js +0 -56
  323. package/dist/vue/kit/primevue/FormField.js.map +0 -1
  324. package/dist/vue/kit/primevue/Icon.js +0 -23
  325. package/dist/vue/kit/primevue/Icon.js.map +0 -1
  326. package/dist/vue/kit/primevue/Input.js +0 -96
  327. package/dist/vue/kit/primevue/Input.js.map +0 -1
  328. package/dist/vue/kit/primevue/InputMenu.js +0 -122
  329. package/dist/vue/kit/primevue/InputMenu.js.map +0 -1
  330. package/dist/vue/kit/primevue/Modal.js +0 -59
  331. package/dist/vue/kit/primevue/Modal.js.map +0 -1
  332. package/dist/vue/kit/primevue/Popover.js +0 -135
  333. package/dist/vue/kit/primevue/Popover.js.map +0 -1
  334. package/dist/vue/kit/primevue/RadioGroup.js +0 -58
  335. package/dist/vue/kit/primevue/RadioGroup.js.map +0 -1
  336. package/dist/vue/kit/primevue/Select.js +0 -68
  337. package/dist/vue/kit/primevue/Select.js.map +0 -1
  338. package/dist/vue/kit/primevue/SelectMenu.js +0 -94
  339. package/dist/vue/kit/primevue/SelectMenu.js.map +0 -1
  340. package/dist/vue/kit/primevue/Separator.js +0 -16
  341. package/dist/vue/kit/primevue/Separator.js.map +0 -1
  342. package/dist/vue/kit/primevue/Switch.js +0 -46
  343. package/dist/vue/kit/primevue/Switch.js.map +0 -1
  344. package/dist/vue/kit/primevue/Table.js +0 -54
  345. package/dist/vue/kit/primevue/Table.js.map +0 -1
  346. package/dist/vue/kit/primevue/Tabs.js +0 -49
  347. package/dist/vue/kit/primevue/Tabs.js.map +0 -1
  348. package/dist/vue/kit/primevue/Textarea.js +0 -55
  349. package/dist/vue/kit/primevue/Textarea.js.map +0 -1
  350. package/dist/vue/kit/primevue/Tooltip.js +0 -43
  351. package/dist/vue/kit/primevue/Tooltip.js.map +0 -1
  352. package/dist/vue/kit/primevue/Tree.js +0 -75
  353. package/dist/vue/kit/primevue/Tree.js.map +0 -1
  354. package/dist/vue/kit/primevue/select-options.js +0 -55
  355. package/dist/vue/kit/primevue/select-options.js.map +0 -1
  356. package/dist/vue/kit/primevue/shared.js +0 -49
  357. package/dist/vue/kit/primevue/shared.js.map +0 -1
  358. package/dist/vue/kit/primevue/trigger.js +0 -25
  359. package/dist/vue/kit/primevue/trigger.js.map +0 -1
  360. package/dist/vue/kit/primevue.css +0 -86
  361. package/dist/vue/kit/primevue.d.ts +0 -33
  362. package/dist/vue/kit/primevue.js +0 -96
  363. package/dist/vue/kit/primevue.js.map +0 -1
@@ -0,0 +1,108 @@
1
+ import { settingsKey } from "./shared.js";
2
+ import { HttpError } from "../server/errors.js";
3
+ import { authenticated, empty, json } from "../server/handlers/context.js";
4
+ import { handleCommentsRequest } from "./comments/server.js";
5
+ //#region src/adapters/documents.ts
6
+ const isDocument = (value) => typeof value === "object" && value !== null && "type" in value && value.type === "doc";
7
+ function documentId(request) {
8
+ const id = request.params?.id;
9
+ if (!id) throw new HttpError(400, "Dokument-ID fehlt");
10
+ return id;
11
+ }
12
+ /** The user and what they may do with document `id`: 403 when the adapter's `authorize` refuses. */
13
+ async function accessOf(ctx, request, id) {
14
+ const user = await authenticated(ctx, request);
15
+ const access = ctx.adapter.authorize ? await ctx.adapter.authorize(user, id) : { readOnly: false };
16
+ if (!access) throw new HttpError(403, "Kein Zugriff auf dieses Dokument");
17
+ return {
18
+ user,
19
+ readOnly: access.readOnly
20
+ };
21
+ }
22
+ /**
23
+ * The document's TipTap JSON: the live content when the collaboration server has the document open (unsaved edits
24
+ * included), else what the adapter stores; `404` when there is none.
25
+ */
26
+ async function documentGet(ctx, request) {
27
+ const id = documentId(request);
28
+ await accessOf(ctx, request, id);
29
+ const content = ctx.collab?.liveContent(id) ?? await ctx.adapter.loadContent(id);
30
+ if (!content) throw new HttpError(404, "Dokument nicht gefunden");
31
+ return json(content);
32
+ }
33
+ /**
34
+ * Stores the request's TipTap JSON (`400` for anything but a `{ type: 'doc' }` object, `403` for a read-only user).
35
+ * Creates the document on adapters that manage documents (`putDocument`); on the others only existing documents can be saved.
36
+ */
37
+ async function documentPut(ctx, request) {
38
+ const id = documentId(request);
39
+ const { readOnly } = await accessOf(ctx, request, id);
40
+ if (readOnly) throw new HttpError(403, "Dokument ist schreibgeschützt");
41
+ let body;
42
+ try {
43
+ body = await request.readJson?.();
44
+ } catch {
45
+ throw new HttpError(400, "Ungültiger JSON-Body");
46
+ }
47
+ if (!isDocument(body)) throw new HttpError(400, "Der Body muss ein TipTap-Dokument sein ({ \"type\": \"doc\", … })");
48
+ if (ctx.adapter.putDocument) await ctx.adapter.putDocument(id, body);
49
+ else if (await ctx.adapter.loadContent(id)) await ctx.adapter.storeContent(id, body);
50
+ else throw new HttpError(404, "Dokument nicht gefunden");
51
+ return empty();
52
+ }
53
+ /** `GET`: the document's `Discussion`. */
54
+ async function commentsGet(ctx, request) {
55
+ const id = documentId(request);
56
+ await accessOf(ctx, request, id);
57
+ const { status, body } = await handleCommentsRequest(ctx.store, {
58
+ method: "GET",
59
+ documentId: id
60
+ });
61
+ return json(body, status);
62
+ }
63
+ const recordOf = (value) => typeof value === "object" && value !== null ? value : {};
64
+ /** The comment authors a `CommentAction` writes (new comments) or touches (edits and deletions of existing ones). */
65
+ async function authorsOf(store, id, body) {
66
+ const authorOf = (comment) => {
67
+ const { authorId } = recordOf(comment);
68
+ return typeof authorId === "string" ? [authorId] : [];
69
+ };
70
+ switch (body.action) {
71
+ case "create": return authorOf(recordOf(body.thread).initialComment);
72
+ case "reply": return authorOf(body.comment);
73
+ case "edit":
74
+ case "remove-comment": {
75
+ const comment = (await store.getDiscussion(id)).threads.find((candidate) => candidate.id === body.threadId)?.comments.find((candidate) => candidate.id === body.commentId);
76
+ return comment ? [comment.authorId] : [];
77
+ }
78
+ default: return [];
79
+ }
80
+ }
81
+ /**
82
+ * `POST` with a `CommentAction` (see `handleCommentsRequest`). The handler trusts its caller, so this route does the
83
+ * authorising: read-only users write nothing, and a user only writes comments under their own id and only edits or
84
+ * deletes their own comments (`403` otherwise).
85
+ */
86
+ async function commentsPost(ctx, request) {
87
+ const id = documentId(request);
88
+ const { user, readOnly } = await accessOf(ctx, request, id);
89
+ if (readOnly) throw new HttpError(403, "Dokument ist schreibgeschützt");
90
+ let body;
91
+ try {
92
+ body = await request.readJson?.();
93
+ } catch {
94
+ throw new HttpError(400, "Ungültiger JSON-Body");
95
+ }
96
+ const self = settingsKey(user);
97
+ if ((await authorsOf(ctx.store, id, recordOf(body))).some((author) => author !== self)) throw new HttpError(403, "Kommentare dürfen nur unter der eigenen Kennung geschrieben und geändert werden");
98
+ const { status, body: answer } = await handleCommentsRequest(ctx.store, {
99
+ method: "POST",
100
+ documentId: id,
101
+ body
102
+ });
103
+ return json(answer, status);
104
+ }
105
+ //#endregion
106
+ export { commentsGet, commentsPost, documentGet, documentPut };
107
+
108
+ //# sourceMappingURL=documents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.js","names":[],"sources":["../../src/adapters/documents.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport { HttpError } from '../server'\nimport { authenticated, empty, type HandlerContext, type HandlerRequest, type HandlerResponse, json } from '../server/handlers'\nimport { settingsKey, type DocumentAdmin } from './shared'\nimport { handleCommentsRequest } from './comments/server'\nimport type { CommentStore, Discussion } from './comments/types'\n\n// Framework-free handlers of the two routes this package adds to the editor's own (`/api/editor/*`): a document's\n// content (what `useEditorDocument` loads and saves) and its comments endpoint. They follow the editor's handler shape\n// (`(ctx, request) => HandlerResponse`, `HttpError` for refusals), so any HTTP server can mount them; `./server/nitro`\n// does so for Nitro. Authentication is the adapter's `authenticate`, per-document access its optional `authorize`.\n\n/** What the document handlers need: the editor's handler context, whose adapter may also manage documents (`json`, `drizzle`). */\nexport interface DocumentsContext extends HandlerContext {\n adapter: HandlerContext['adapter'] & Partial<DocumentAdmin>\n}\n\nconst isDocument = (value: unknown): value is JSONContent => typeof value === 'object' && value !== null && 'type' in value && value.type === 'doc'\n\nfunction documentId(request: HandlerRequest): string {\n const id = request.params?.id\n if (!id) throw new HttpError(400, 'Dokument-ID fehlt')\n return id\n}\n\n/** The user and what they may do with document `id`: 403 when the adapter's `authorize` refuses. */\nasync function accessOf(ctx: DocumentsContext, request: HandlerRequest, id: string): Promise<{ user: object, readOnly: boolean }> {\n const user = await authenticated(ctx, request)\n const access = ctx.adapter.authorize ? await ctx.adapter.authorize(user, id) : { readOnly: false }\n if (!access) throw new HttpError(403, 'Kein Zugriff auf dieses Dokument')\n return { user, readOnly: access.readOnly }\n}\n\n/**\n * The document's TipTap JSON: the live content when the collaboration server has the document open (unsaved edits\n * included), else what the adapter stores; `404` when there is none.\n */\nexport async function documentGet(ctx: DocumentsContext, request: HandlerRequest): Promise<HandlerResponse> {\n const id = documentId(request)\n await accessOf(ctx, request, id)\n const content = ctx.collab?.liveContent(id) ?? await ctx.adapter.loadContent(id)\n if (!content) throw new HttpError(404, 'Dokument nicht gefunden')\n return json(content)\n}\n\n/**\n * Stores the request's TipTap JSON (`400` for anything but a `{ type: 'doc' }` object, `403` for a read-only user).\n * Creates the document on adapters that manage documents (`putDocument`); on the others only existing documents can be saved.\n */\nexport async function documentPut(ctx: DocumentsContext, request: HandlerRequest): Promise<HandlerResponse> {\n const id = documentId(request)\n const { readOnly } = await accessOf(ctx, request, id)\n if (readOnly) throw new HttpError(403, 'Dokument ist schreibgeschützt')\n let body: unknown\n try {\n body = await request.readJson?.()\n }\n catch {\n throw new HttpError(400, 'Ungültiger JSON-Body')\n }\n if (!isDocument(body)) throw new HttpError(400, 'Der Body muss ein TipTap-Dokument sein ({ \"type\": \"doc\", … })')\n if (ctx.adapter.putDocument) await ctx.adapter.putDocument(id, body)\n else if (await ctx.adapter.loadContent(id)) await ctx.adapter.storeContent(id, body)\n else throw new HttpError(404, 'Dokument nicht gefunden')\n return empty()\n}\n\nexport interface CommentsRouteContext extends DocumentsContext {\n store: CommentStore\n}\n\n/** `GET`: the document's `Discussion`. */\nexport async function commentsGet(ctx: CommentsRouteContext, request: HandlerRequest): Promise<HandlerResponse> {\n const id = documentId(request)\n await accessOf(ctx, request, id)\n const { status, body } = await handleCommentsRequest(ctx.store, { method: 'GET', documentId: id })\n return json(body, status)\n}\n\nconst recordOf = (value: unknown): Record<string, unknown> => typeof value === 'object' && value !== null ? value as Record<string, unknown> : {}\n\n/** The comment authors a `CommentAction` writes (new comments) or touches (edits and deletions of existing ones). */\nasync function authorsOf(store: CommentStore, id: string, body: Record<string, unknown>): Promise<string[]> {\n // A malformed comment has no author to compare; the handler answers it with 400.\n const authorOf = (comment: unknown) => {\n const { authorId } = recordOf(comment)\n return typeof authorId === 'string' ? [authorId] : []\n }\n switch (body.action) {\n case 'create':\n return authorOf(recordOf(body.thread).initialComment)\n case 'reply':\n return authorOf(body.comment)\n case 'edit':\n case 'remove-comment': {\n const discussion: Discussion = await store.getDiscussion(id)\n const thread = discussion.threads.find(candidate => candidate.id === body.threadId)\n const comment = thread?.comments.find(candidate => candidate.id === body.commentId)\n // An unknown id is the handler's 404, not an authorisation question.\n return comment ? [comment.authorId] : []\n }\n default:\n return []\n }\n}\n\n/**\n * `POST` with a `CommentAction` (see `handleCommentsRequest`). The handler trusts its caller, so this route does the\n * authorising: read-only users write nothing, and a user only writes comments under their own id and only edits or\n * deletes their own comments (`403` otherwise).\n */\nexport async function commentsPost(ctx: CommentsRouteContext, request: HandlerRequest): Promise<HandlerResponse> {\n const id = documentId(request)\n const { user, readOnly } = await accessOf(ctx, request, id)\n if (readOnly) throw new HttpError(403, 'Dokument ist schreibgeschützt')\n let body: unknown\n try {\n body = await request.readJson?.()\n }\n catch {\n throw new HttpError(400, 'Ungültiger JSON-Body')\n }\n const self = settingsKey(user)\n if ((await authorsOf(ctx.store, id, recordOf(body))).some(author => author !== self)) throw new HttpError(403, 'Kommentare dürfen nur unter der eigenen Kennung geschrieben und geändert werden')\n const { status, body: answer } = await handleCommentsRequest(ctx.store, { method: 'POST', documentId: id, body })\n return json(answer, status)\n}\n"],"mappings":"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"}
@@ -0,0 +1,293 @@
1
+ import { AssetRecord } from "../features/media/assets.js";
2
+ import "../features/media/schema.js";
3
+ import { AdapterHooks, StoredEditorAdapter } from "./shared.js";
4
+ import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";
5
+ import { JSONContent } from "@tiptap/core";
6
+ //#region src/adapters/drizzle.d.ts
7
+ declare const EDITOR_TABLES: {
8
+ documents: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
9
+ name: "editor_documents";
10
+ schema: undefined;
11
+ columns: {
12
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
13
+ name: "id";
14
+ tableName: "editor_documents";
15
+ dataType: "string";
16
+ columnType: "SQLiteText";
17
+ data: string;
18
+ driverParam: string;
19
+ notNull: true;
20
+ hasDefault: false;
21
+ isPrimaryKey: true;
22
+ isAutoincrement: false;
23
+ hasRuntimeDefault: false;
24
+ enumValues: [string, ...string[]];
25
+ baseColumn: never;
26
+ identity: undefined;
27
+ generated: undefined;
28
+ }, {}, {
29
+ length: number | undefined;
30
+ }>;
31
+ content: import("drizzle-orm/sqlite-core").SQLiteColumn<{
32
+ name: "content";
33
+ tableName: "editor_documents";
34
+ dataType: "json";
35
+ columnType: "SQLiteTextJson";
36
+ data: JSONContent;
37
+ driverParam: string;
38
+ notNull: true;
39
+ hasDefault: false;
40
+ isPrimaryKey: false;
41
+ isAutoincrement: false;
42
+ hasRuntimeDefault: false;
43
+ enumValues: undefined;
44
+ baseColumn: never;
45
+ identity: undefined;
46
+ generated: undefined;
47
+ }, {}, {
48
+ $type: JSONContent;
49
+ }>;
50
+ version: import("drizzle-orm/sqlite-core").SQLiteColumn<{
51
+ name: "version";
52
+ tableName: "editor_documents";
53
+ dataType: "string";
54
+ columnType: "SQLiteText";
55
+ data: string;
56
+ driverParam: string;
57
+ notNull: true;
58
+ hasDefault: false;
59
+ isPrimaryKey: false;
60
+ isAutoincrement: false;
61
+ hasRuntimeDefault: false;
62
+ enumValues: [string, ...string[]];
63
+ baseColumn: never;
64
+ identity: undefined;
65
+ generated: undefined;
66
+ }, {}, {
67
+ length: number | undefined;
68
+ }>;
69
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
70
+ name: "updated_at";
71
+ tableName: "editor_documents";
72
+ dataType: "string";
73
+ columnType: "SQLiteText";
74
+ data: string;
75
+ driverParam: string;
76
+ notNull: true;
77
+ hasDefault: false;
78
+ isPrimaryKey: false;
79
+ isAutoincrement: false;
80
+ hasRuntimeDefault: false;
81
+ enumValues: [string, ...string[]];
82
+ baseColumn: never;
83
+ identity: undefined;
84
+ generated: undefined;
85
+ }, {}, {
86
+ length: number | undefined;
87
+ }>;
88
+ };
89
+ dialect: "sqlite";
90
+ }>;
91
+ states: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
92
+ name: "editor_states";
93
+ schema: undefined;
94
+ columns: {
95
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
96
+ name: "id";
97
+ tableName: "editor_states";
98
+ dataType: "string";
99
+ columnType: "SQLiteText";
100
+ data: string;
101
+ driverParam: string;
102
+ notNull: true;
103
+ hasDefault: false;
104
+ isPrimaryKey: true;
105
+ isAutoincrement: false;
106
+ hasRuntimeDefault: false;
107
+ enumValues: [string, ...string[]];
108
+ baseColumn: never;
109
+ identity: undefined;
110
+ generated: undefined;
111
+ }, {}, {
112
+ length: number | undefined;
113
+ }>;
114
+ state: import("drizzle-orm/sqlite-core").SQLiteColumn<{
115
+ name: "state";
116
+ tableName: "editor_states";
117
+ dataType: "buffer";
118
+ columnType: "SQLiteBlobBuffer";
119
+ data: Buffer<ArrayBufferLike>;
120
+ driverParam: Buffer<ArrayBufferLike>;
121
+ notNull: true;
122
+ hasDefault: false;
123
+ isPrimaryKey: false;
124
+ isAutoincrement: false;
125
+ hasRuntimeDefault: false;
126
+ enumValues: undefined;
127
+ baseColumn: never;
128
+ identity: undefined;
129
+ generated: undefined;
130
+ }, {}, {}>;
131
+ };
132
+ dialect: "sqlite";
133
+ }>;
134
+ settings: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
135
+ name: "editor_settings";
136
+ schema: undefined;
137
+ columns: {
138
+ userId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
139
+ name: "user_id";
140
+ tableName: "editor_settings";
141
+ dataType: "string";
142
+ columnType: "SQLiteText";
143
+ data: string;
144
+ driverParam: string;
145
+ notNull: true;
146
+ hasDefault: false;
147
+ isPrimaryKey: true;
148
+ isAutoincrement: false;
149
+ hasRuntimeDefault: false;
150
+ enumValues: [string, ...string[]];
151
+ baseColumn: never;
152
+ identity: undefined;
153
+ generated: undefined;
154
+ }, {}, {
155
+ length: number | undefined;
156
+ }>;
157
+ json: import("drizzle-orm/sqlite-core").SQLiteColumn<{
158
+ name: "json";
159
+ tableName: "editor_settings";
160
+ dataType: "json";
161
+ columnType: "SQLiteTextJson";
162
+ data: unknown;
163
+ driverParam: string;
164
+ notNull: true;
165
+ hasDefault: false;
166
+ isPrimaryKey: false;
167
+ isAutoincrement: false;
168
+ hasRuntimeDefault: false;
169
+ enumValues: undefined;
170
+ baseColumn: never;
171
+ identity: undefined;
172
+ generated: undefined;
173
+ }, {}, {
174
+ $type: unknown;
175
+ }>;
176
+ };
177
+ dialect: "sqlite";
178
+ }>;
179
+ assets: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
180
+ name: "editor_assets";
181
+ schema: undefined;
182
+ columns: {
183
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
184
+ name: "id";
185
+ tableName: "editor_assets";
186
+ dataType: "string";
187
+ columnType: "SQLiteText";
188
+ data: string;
189
+ driverParam: string;
190
+ notNull: true;
191
+ hasDefault: false;
192
+ isPrimaryKey: false;
193
+ isAutoincrement: false;
194
+ hasRuntimeDefault: false;
195
+ enumValues: [string, ...string[]];
196
+ baseColumn: never;
197
+ identity: undefined;
198
+ generated: undefined;
199
+ }, {}, {
200
+ length: number | undefined;
201
+ }>;
202
+ name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
203
+ name: "name";
204
+ tableName: "editor_assets";
205
+ dataType: "string";
206
+ columnType: "SQLiteText";
207
+ data: string;
208
+ driverParam: string;
209
+ notNull: true;
210
+ hasDefault: false;
211
+ isPrimaryKey: false;
212
+ isAutoincrement: false;
213
+ hasRuntimeDefault: false;
214
+ enumValues: [string, ...string[]];
215
+ baseColumn: never;
216
+ identity: undefined;
217
+ generated: undefined;
218
+ }, {}, {
219
+ length: number | undefined;
220
+ }>;
221
+ record: import("drizzle-orm/sqlite-core").SQLiteColumn<{
222
+ name: "record";
223
+ tableName: "editor_assets";
224
+ dataType: "json";
225
+ columnType: "SQLiteTextJson";
226
+ data: AssetRecord;
227
+ driverParam: string;
228
+ notNull: true;
229
+ hasDefault: false;
230
+ isPrimaryKey: false;
231
+ isAutoincrement: false;
232
+ hasRuntimeDefault: false;
233
+ enumValues: undefined;
234
+ baseColumn: never;
235
+ identity: undefined;
236
+ generated: undefined;
237
+ }, {}, {
238
+ $type: AssetRecord;
239
+ }>;
240
+ data: import("drizzle-orm/sqlite-core").SQLiteColumn<{
241
+ name: "data";
242
+ tableName: "editor_assets";
243
+ dataType: "buffer";
244
+ columnType: "SQLiteBlobBuffer";
245
+ data: Buffer<ArrayBufferLike>;
246
+ driverParam: Buffer<ArrayBufferLike>;
247
+ notNull: true;
248
+ hasDefault: false;
249
+ isPrimaryKey: false;
250
+ isAutoincrement: false;
251
+ hasRuntimeDefault: false;
252
+ enumValues: undefined;
253
+ baseColumn: never;
254
+ identity: undefined;
255
+ generated: undefined;
256
+ }, {}, {}>;
257
+ };
258
+ dialect: "sqlite";
259
+ }>;
260
+ };
261
+ export type EditorTables = typeof EDITOR_TABLES;
262
+ /**
263
+ * The tables of the editor's storage in a SQLite database (`bun:sqlite`, better-sqlite3, libsql, D1):
264
+ *
265
+ * editor_documents(id PK, content json, version text, updated_at text) TipTap JSON + opaque content version
266
+ * editor_states(id PK, state blob) the collab server's state bytes
267
+ * editor_settings(user_id PK, json json) per-user editor settings
268
+ * editor_assets(id, name, record json, data blob; PK (id, name)) uploaded files
269
+ *
270
+ * Call it from the host's drizzle schema (so `drizzle-kit` migrates the tables) or use `bootstrapEditorTables` in a
271
+ * host that creates its tables with `CREATE TABLE IF NOT EXISTS`.
272
+ */
273
+ export declare const editorTables: () => EditorTables;
274
+ /** Any drizzle SQLite database, sync (`bun:sqlite`, better-sqlite3) or async (libsql, D1). */
275
+ export type EditorDb = BaseSQLiteDatabase<'sync' | 'async', unknown, Record<string, unknown>>;
276
+ /** `CREATE TABLE IF NOT EXISTS` for the tables of `editorTables()`, for hosts without migrations. */
277
+ export declare function bootstrapEditorTables(db: EditorDb): Promise<void>;
278
+ export interface DrizzleAdapterOptions extends AdapterHooks {
279
+ db: EditorDb;
280
+ tables: EditorTables;
281
+ }
282
+ /**
283
+ * The editor's adapter on drizzle tables. Every write to a document's content (`storeContent`, `putDocument`) stores a
284
+ * fresh random `version`, which `contentVersion` returns: a document changed by any other writer keeps its row's
285
+ * version only if that writer left it alone, so hosts that edit `editor_documents` themselves must go through
286
+ * `putDocument` (or set `version` to anything new). `storeContent` only updates existing rows: create documents with
287
+ * `putDocument`. Single-statement operations only (no transaction), so the adapter runs on sync and async drivers;
288
+ * `deleteDocument` removes content first, so a concurrent `storeContent` finds nothing to update. No theme library.
289
+ */
290
+ export declare function createDrizzleAdapter({ db, tables, ...hooks }: DrizzleAdapterOptions): StoredEditorAdapter;
291
+ //#endregion
292
+ export {};
293
+ //# sourceMappingURL=drizzle.d.ts.map
@@ -0,0 +1,157 @@
1
+ import { bytesOf, identityPart, settingsKey } from "./shared.js";
2
+ import { randomUUID } from "node:crypto";
3
+ import { and, eq, sql } from "drizzle-orm";
4
+ import { blob, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
5
+ //#region src/adapters/drizzle.ts
6
+ const EDITOR_TABLES = {
7
+ documents: sqliteTable("editor_documents", {
8
+ id: text("id").primaryKey(),
9
+ content: text("content", { mode: "json" }).$type().notNull(),
10
+ version: text("version").notNull(),
11
+ updatedAt: text("updated_at").notNull()
12
+ }),
13
+ states: sqliteTable("editor_states", {
14
+ id: text("id").primaryKey(),
15
+ state: blob("state", { mode: "buffer" }).notNull()
16
+ }),
17
+ settings: sqliteTable("editor_settings", {
18
+ userId: text("user_id").primaryKey(),
19
+ json: text("json", { mode: "json" }).$type().notNull()
20
+ }),
21
+ assets: sqliteTable("editor_assets", {
22
+ id: text("id").notNull(),
23
+ name: text("name").notNull(),
24
+ record: text("record", { mode: "json" }).$type().notNull(),
25
+ data: blob("data", { mode: "buffer" }).notNull()
26
+ }, (table) => [primaryKey({ columns: [table.id, table.name] })])
27
+ };
28
+ /**
29
+ * The tables of the editor's storage in a SQLite database (`bun:sqlite`, better-sqlite3, libsql, D1):
30
+ *
31
+ * editor_documents(id PK, content json, version text, updated_at text) TipTap JSON + opaque content version
32
+ * editor_states(id PK, state blob) the collab server's state bytes
33
+ * editor_settings(user_id PK, json json) per-user editor settings
34
+ * editor_assets(id, name, record json, data blob; PK (id, name)) uploaded files
35
+ *
36
+ * Call it from the host's drizzle schema (so `drizzle-kit` migrates the tables) or use `bootstrapEditorTables` in a
37
+ * host that creates its tables with `CREATE TABLE IF NOT EXISTS`.
38
+ */
39
+ const editorTables = () => EDITOR_TABLES;
40
+ /** `CREATE TABLE IF NOT EXISTS` for the tables of `editorTables()`, for hosts without migrations. */
41
+ async function bootstrapEditorTables(db) {
42
+ await db.run(sql`CREATE TABLE IF NOT EXISTS editor_documents (id text PRIMARY KEY NOT NULL, content text NOT NULL, version text NOT NULL, updated_at text NOT NULL)`);
43
+ await db.run(sql`CREATE TABLE IF NOT EXISTS editor_states (id text PRIMARY KEY NOT NULL, state blob NOT NULL)`);
44
+ await db.run(sql`CREATE TABLE IF NOT EXISTS editor_settings (user_id text PRIMARY KEY NOT NULL, json text NOT NULL)`);
45
+ await db.run(sql`CREATE TABLE IF NOT EXISTS editor_assets (id text NOT NULL, name text NOT NULL, record text NOT NULL, data blob NOT NULL, PRIMARY KEY (id, name))`);
46
+ }
47
+ /**
48
+ * The editor's adapter on drizzle tables. Every write to a document's content (`storeContent`, `putDocument`) stores a
49
+ * fresh random `version`, which `contentVersion` returns: a document changed by any other writer keeps its row's
50
+ * version only if that writer left it alone, so hosts that edit `editor_documents` themselves must go through
51
+ * `putDocument` (or set `version` to anything new). `storeContent` only updates existing rows: create documents with
52
+ * `putDocument`. Single-statement operations only (no transaction), so the adapter runs on sync and async drivers;
53
+ * `deleteDocument` removes content first, so a concurrent `storeContent` finds nothing to update. No theme library.
54
+ */
55
+ function createDrizzleAdapter({ db, tables, ...hooks }) {
56
+ const { documents, states, settings, assets } = tables;
57
+ const documentContent = async (id) => (await db.select({ content: documents.content }).from(documents).where(eq(documents.id, id)).get())?.content ?? null;
58
+ return {
59
+ ...identityPart(hooks),
60
+ loadContent: documentContent,
61
+ async storeContent(id, content) {
62
+ await db.update(documents).set({
63
+ content,
64
+ version: randomUUID(),
65
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
66
+ }).where(eq(documents.id, id)).run();
67
+ },
68
+ async contentVersion(id) {
69
+ return (await db.select({ version: documents.version }).from(documents).where(eq(documents.id, id)).get())?.version ?? null;
70
+ },
71
+ async listContents() {
72
+ return db.select({
73
+ id: documents.id,
74
+ content: documents.content
75
+ }).from(documents).orderBy(documents.id).all();
76
+ },
77
+ async loadState(id) {
78
+ const row = await db.select({ state: states.state }).from(states).where(eq(states.id, id)).get();
79
+ return row ? bytesOf(row.state) : null;
80
+ },
81
+ async storeState(id, state) {
82
+ const data = Buffer.from(state);
83
+ await db.insert(states).values({
84
+ id,
85
+ state: data
86
+ }).onConflictDoUpdate({
87
+ target: states.id,
88
+ set: { state: data }
89
+ }).run();
90
+ },
91
+ async removeState(id) {
92
+ await db.delete(states).where(eq(states.id, id)).run();
93
+ },
94
+ async storeAsset(id, { data, ...record }) {
95
+ await db.insert(assets).values({
96
+ id,
97
+ name: record.name,
98
+ record,
99
+ data
100
+ }).onConflictDoUpdate({
101
+ target: [assets.id, assets.name],
102
+ set: {
103
+ record,
104
+ data
105
+ }
106
+ }).run();
107
+ },
108
+ async loadAsset(id, name) {
109
+ const row = await db.select({
110
+ record: assets.record,
111
+ data: assets.data
112
+ }).from(assets).where(and(eq(assets.id, id), eq(assets.name, name))).get();
113
+ return row ? {
114
+ record: row.record,
115
+ data: Buffer.from(row.data)
116
+ } : null;
117
+ },
118
+ async loadSettings(user) {
119
+ return (await db.select({ json: settings.json }).from(settings).where(eq(settings.userId, settingsKey(user))).get())?.json ?? null;
120
+ },
121
+ async storeSettings(user, value) {
122
+ await db.insert(settings).values({
123
+ userId: settingsKey(user),
124
+ json: value
125
+ }).onConflictDoUpdate({
126
+ target: settings.userId,
127
+ set: { json: value }
128
+ }).run();
129
+ },
130
+ async putDocument(id, content) {
131
+ const row = {
132
+ content,
133
+ version: randomUUID(),
134
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
135
+ };
136
+ await db.insert(documents).values({
137
+ id,
138
+ ...row
139
+ }).onConflictDoUpdate({
140
+ target: documents.id,
141
+ set: row
142
+ }).run();
143
+ },
144
+ async deleteDocument(id) {
145
+ await db.delete(documents).where(eq(documents.id, id)).run();
146
+ await db.delete(states).where(eq(states.id, id)).run();
147
+ await db.delete(assets).where(eq(assets.id, id)).run();
148
+ },
149
+ async listDocumentIds() {
150
+ return (await db.select({ id: documents.id }).from(documents).orderBy(documents.id).all()).map((row) => row.id);
151
+ }
152
+ };
153
+ }
154
+ //#endregion
155
+ export { bootstrapEditorTables, createDrizzleAdapter, editorTables };
156
+
157
+ //# sourceMappingURL=drizzle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle.js","names":[],"sources":["../../src/adapters/drizzle.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport type { JSONContent } from '@tiptap/core'\nimport type { AssetRecord } from '../features/media/schema'\nimport { and, eq, sql } from 'drizzle-orm'\nimport { type BaseSQLiteDatabase, blob, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core'\nimport { type AdapterHooks, bytesOf, identityPart, settingsKey, type StoredEditorAdapter } from './shared'\n\nconst EDITOR_TABLES = {\n documents: sqliteTable('editor_documents', {\n id: text('id').primaryKey(),\n content: text('content', { mode: 'json' }).$type<JSONContent>().notNull(),\n version: text('version').notNull(),\n updatedAt: text('updated_at').notNull(),\n }),\n states: sqliteTable('editor_states', {\n id: text('id').primaryKey(),\n state: blob('state', { mode: 'buffer' }).notNull(),\n }),\n settings: sqliteTable('editor_settings', {\n userId: text('user_id').primaryKey(),\n json: text('json', { mode: 'json' }).$type<unknown>().notNull(),\n }),\n assets: sqliteTable('editor_assets', {\n id: text('id').notNull(),\n name: text('name').notNull(),\n record: text('record', { mode: 'json' }).$type<AssetRecord>().notNull(),\n data: blob('data', { mode: 'buffer' }).notNull(),\n }, table => [primaryKey({ columns: [table.id, table.name] })]),\n}\n\nexport type EditorTables = typeof EDITOR_TABLES\n\n/**\n * The tables of the editor's storage in a SQLite database (`bun:sqlite`, better-sqlite3, libsql, D1):\n *\n * editor_documents(id PK, content json, version text, updated_at text) TipTap JSON + opaque content version\n * editor_states(id PK, state blob) the collab server's state bytes\n * editor_settings(user_id PK, json json) per-user editor settings\n * editor_assets(id, name, record json, data blob; PK (id, name)) uploaded files\n *\n * Call it from the host's drizzle schema (so `drizzle-kit` migrates the tables) or use `bootstrapEditorTables` in a\n * host that creates its tables with `CREATE TABLE IF NOT EXISTS`.\n */\nexport const editorTables = (): EditorTables => EDITOR_TABLES\n\n/** Any drizzle SQLite database, sync (`bun:sqlite`, better-sqlite3) or async (libsql, D1). */\nexport type EditorDb = BaseSQLiteDatabase<'sync' | 'async', unknown, Record<string, unknown>>\n\n/** `CREATE TABLE IF NOT EXISTS` for the tables of `editorTables()`, for hosts without migrations. */\nexport async function bootstrapEditorTables(db: EditorDb): Promise<void> {\n await db.run(sql`CREATE TABLE IF NOT EXISTS editor_documents (id text PRIMARY KEY NOT NULL, content text NOT NULL, version text NOT NULL, updated_at text NOT NULL)`)\n await db.run(sql`CREATE TABLE IF NOT EXISTS editor_states (id text PRIMARY KEY NOT NULL, state blob NOT NULL)`)\n await db.run(sql`CREATE TABLE IF NOT EXISTS editor_settings (user_id text PRIMARY KEY NOT NULL, json text NOT NULL)`)\n await db.run(sql`CREATE TABLE IF NOT EXISTS editor_assets (id text NOT NULL, name text NOT NULL, record text NOT NULL, data blob NOT NULL, PRIMARY KEY (id, name))`)\n}\n\nexport interface DrizzleAdapterOptions extends AdapterHooks {\n db: EditorDb\n tables: EditorTables\n}\n\n/**\n * The editor's adapter on drizzle tables. Every write to a document's content (`storeContent`, `putDocument`) stores a\n * fresh random `version`, which `contentVersion` returns: a document changed by any other writer keeps its row's\n * version only if that writer left it alone, so hosts that edit `editor_documents` themselves must go through\n * `putDocument` (or set `version` to anything new). `storeContent` only updates existing rows: create documents with\n * `putDocument`. Single-statement operations only (no transaction), so the adapter runs on sync and async drivers;\n * `deleteDocument` removes content first, so a concurrent `storeContent` finds nothing to update. No theme library.\n */\nexport function createDrizzleAdapter({ db, tables, ...hooks }: DrizzleAdapterOptions): StoredEditorAdapter {\n const { documents, states, settings, assets } = tables\n\n const documentContent = async (id: string) => (await db.select({ content: documents.content }).from(documents).where(eq(documents.id, id)).get())?.content ?? null\n\n return {\n ...identityPart(hooks),\n\n loadContent: documentContent,\n async storeContent(id, content) {\n await db.update(documents).set({ content, version: randomUUID(), updatedAt: new Date().toISOString() }).where(eq(documents.id, id)).run()\n },\n async contentVersion(id) {\n return (await db.select({ version: documents.version }).from(documents).where(eq(documents.id, id)).get())?.version ?? null\n },\n async listContents() {\n return db.select({ id: documents.id, content: documents.content }).from(documents).orderBy(documents.id).all()\n },\n\n async loadState(id) {\n const row = await db.select({ state: states.state }).from(states).where(eq(states.id, id)).get()\n return row ? bytesOf(row.state) : null\n },\n async storeState(id, state) {\n const data = Buffer.from(state)\n await db.insert(states).values({ id, state: data }).onConflictDoUpdate({ target: states.id, set: { state: data } }).run()\n },\n async removeState(id) {\n await db.delete(states).where(eq(states.id, id)).run()\n },\n\n async storeAsset(id, { data, ...record }) {\n await db.insert(assets).values({ id, name: record.name, record, data })\n .onConflictDoUpdate({ target: [assets.id, assets.name], set: { record, data } }).run()\n },\n async loadAsset(id, name) {\n const row = await db.select({ record: assets.record, data: assets.data }).from(assets).where(and(eq(assets.id, id), eq(assets.name, name))).get()\n return row ? { record: row.record, data: Buffer.from(row.data) } : null\n },\n\n async loadSettings(user) {\n return (await db.select({ json: settings.json }).from(settings).where(eq(settings.userId, settingsKey(user))).get())?.json ?? null\n },\n async storeSettings(user, value) {\n await db.insert(settings).values({ userId: settingsKey(user), json: value })\n .onConflictDoUpdate({ target: settings.userId, set: { json: value } }).run()\n },\n\n async putDocument(id, content) {\n const row = { content, version: randomUUID(), updatedAt: new Date().toISOString() }\n await db.insert(documents).values({ id, ...row }).onConflictDoUpdate({ target: documents.id, set: row }).run()\n },\n async deleteDocument(id) {\n await db.delete(documents).where(eq(documents.id, id)).run()\n await db.delete(states).where(eq(states.id, id)).run()\n await db.delete(assets).where(eq(assets.id, id)).run()\n },\n async listDocumentIds() {\n return (await db.select({ id: documents.id }).from(documents).orderBy(documents.id).all()).map(row => row.id)\n },\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { promises } from "node:fs";
3
+ import path from "node:path";
4
+ //#region src/adapters/files.ts
5
+ const isMissing = (error) => error.code === "ENOENT";
6
+ /** Writes through a temp file in the same directory and renames it into place: a reader sees the old or the new file, never a torn one. */
7
+ async function writeAtomic(file, data) {
8
+ await promises.mkdir(path.dirname(file), { recursive: true });
9
+ const temp = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`);
10
+ try {
11
+ await promises.writeFile(temp, data);
12
+ await promises.rename(temp, file);
13
+ } catch (error) {
14
+ await promises.rm(temp, { force: true });
15
+ throw error;
16
+ }
17
+ }
18
+ /** The bytes of `file`, `null` when it does not exist. */
19
+ async function readOrNull(file) {
20
+ try {
21
+ return await promises.readFile(file);
22
+ } catch (error) {
23
+ if (isMissing(error)) return null;
24
+ throw error;
25
+ }
26
+ }
27
+ //#endregion
28
+ export { isMissing, readOrNull, writeAtomic };
29
+
30
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","names":["fs"],"sources":["../../src/adapters/files.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nexport const isMissing = (error: unknown) => (error as NodeJS.ErrnoException).code === 'ENOENT'\n\n/** Writes through a temp file in the same directory and renames it into place: a reader sees the old or the new file, never a torn one. */\nexport async function writeAtomic(file: string, data: string | Uint8Array): Promise<void> {\n await fs.mkdir(path.dirname(file), { recursive: true })\n const temp = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`)\n try {\n await fs.writeFile(temp, data)\n await fs.rename(temp, file)\n }\n catch (error) {\n await fs.rm(temp, { force: true })\n throw error\n }\n}\n\n/** The bytes of `file`, `null` when it does not exist. */\nexport async function readOrNull(file: string): Promise<Buffer | null> {\n try {\n return await fs.readFile(file)\n }\n catch (error) {\n if (isMissing(error)) return null\n throw error\n }\n}\n"],"mappings":";;;;AAIA,MAAa,aAAa,UAAoB,MAAgC,SAAS;;AAGvF,eAAsB,YAAY,MAAc,MAA0C;CACxF,MAAMA,SAAG,MAAM,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CACtD,MAAM,OAAO,KAAK,KAAK,KAAK,QAAQ,IAAI,GAAG,IAAI,KAAK,SAAS,IAAI,EAAE,GAAG,WAAW,EAAE,KAAK;CACxF,IAAI;EACF,MAAMA,SAAG,UAAU,MAAM,IAAI;EAC7B,MAAMA,SAAG,OAAO,MAAM,IAAI;CAC5B,SACO,OAAO;EACZ,MAAMA,SAAG,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;EACjC,MAAM;CACR;AACF;;AAGA,eAAsB,WAAW,MAAsC;CACrE,IAAI;EACF,OAAO,MAAMA,SAAG,SAAS,IAAI;CAC/B,SACO,OAAO;EACZ,IAAI,UAAU,KAAK,GAAG,OAAO;EAC7B,MAAM;CACR;AACF"}