alinea 2.0.0-preview.13 → 2.0.0-preview.14

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 (323) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.mcp/later/dev.json +1 -1
  3. package/dist/.mcp/online/dev.json +1 -1
  4. package/dist/adapter/next/cms.js +6 -2
  5. package/dist/adapter/next/context.js +1 -4
  6. package/dist/adapter/next/handler.js +12 -1
  7. package/dist/backend/CommitEntries.d.ts +17 -0
  8. package/dist/backend/CommitEntries.js +39 -0
  9. package/dist/backend/Handler.d.ts +9 -0
  10. package/dist/backend/Handler.js +80 -20
  11. package/dist/backend/api/BasicAuth.js +5 -1
  12. package/dist/backend/api/CreateBackend.js +2 -2
  13. package/dist/backend/api/DatabaseApi.js +18 -5
  14. package/dist/backend/api/GithubApi.d.ts +13 -0
  15. package/dist/backend/api/GithubApi.js +170 -30
  16. package/dist/backend/api/database.js +2 -2
  17. package/dist/backend/util/Syncable.d.ts +6 -0
  18. package/dist/backend/util/Syncable.js +3 -1
  19. package/dist/bundled.d.ts +911 -151
  20. package/dist/chunks/{chunk-5H4D5JQL.js → chunk-2PP7WJUJ.js} +1 -11
  21. package/dist/chunks/{chunk-QHHMKJUO.js → chunk-7DDPSWRY.js} +465 -2
  22. package/dist/chunks/{chunk-HOETF6HN.js → chunk-PMGIIYDD.js} +1 -0
  23. package/dist/chunks/{chunk-3QFSGTGF.js → chunk-SCV3TEXM.js} +1 -1
  24. package/dist/chunks/{chunk-UPY2RUZF.js → chunk-TWBREZWC.js} +30 -0
  25. package/dist/cli/Generate.js +20 -7
  26. package/dist/cli/Mcp.js +1 -1
  27. package/dist/cli/Serve.js +1 -1
  28. package/dist/cli/bin.js +1 -1
  29. package/dist/cli/build/BuildEmitter.d.ts +5 -1
  30. package/dist/cli/build/BuildEmitter.js +8 -3
  31. package/dist/cli/generate/CompileConfig.js +10 -6
  32. package/dist/cli/generate/DevDB.d.ts +4 -1
  33. package/dist/cli/generate/DevDB.js +15 -7
  34. package/dist/cli/generate/GenerateDashboard.js +1 -0
  35. package/dist/cli/generate/MediaLocations.d.ts +18 -0
  36. package/dist/cli/generate/MediaLocations.js +69 -0
  37. package/dist/cli/serve/CreateLocalServer.js +1 -0
  38. package/dist/cli/serve/mcp/DevMcp.js +1 -1
  39. package/dist/cloud/CloudRemote.js +21 -7
  40. package/dist/cms.d.ts +4 -2
  41. package/dist/components/Alert.js +2 -2
  42. package/dist/components/AppShell.js +2 -2
  43. package/dist/components/AsyncList.d.ts +52 -0
  44. package/dist/components/AsyncList.js +69 -0
  45. package/dist/components/Breadcrumbs.js +6 -6
  46. package/dist/components/Button.js +4 -4
  47. package/dist/components/Calendar.js +18 -18
  48. package/dist/components/Checkbox.js +10 -10
  49. package/dist/components/CheckboxGroup.js +2 -2
  50. package/dist/components/Collapsible.js +5 -5
  51. package/dist/components/ColorSwatch.js +2 -2
  52. package/dist/components/ColorSwatchPicker.js +4 -4
  53. package/dist/components/ComboBox.d.ts +27 -9
  54. package/dist/components/ComboBox.js +40 -9
  55. package/dist/components/Command.js +13 -13
  56. package/dist/components/ContentCard.js +28 -28
  57. package/dist/components/ContentGrid.js +11 -11
  58. package/dist/components/DataList.js +1 -1
  59. package/dist/components/DateField.d.ts +4 -0
  60. package/dist/components/DateField.js +2 -2
  61. package/dist/components/DatePicker.js +2 -2
  62. package/dist/components/DateRangePicker.js +2 -2
  63. package/dist/components/Dialog.d.ts +14 -3
  64. package/dist/components/Dialog.js +38 -11
  65. package/dist/components/DropZone.js +6 -6
  66. package/dist/components/DropdownMenu.js +8 -8
  67. package/dist/components/Empty.js +4 -4
  68. package/dist/components/Field.d.ts +1 -1
  69. package/dist/components/Field.js +18 -12
  70. package/dist/components/Form.d.ts +23 -0
  71. package/dist/components/Form.js +64 -0
  72. package/dist/components/Link.d.ts +8 -1
  73. package/dist/components/List.js +27 -27
  74. package/dist/components/MediaPreview.js +5 -5
  75. package/dist/components/MultipleSelect.d.ts +13 -8
  76. package/dist/components/MultipleSelect.js +25 -25
  77. package/dist/components/NavRail.js +7 -7
  78. package/dist/components/NumberField.d.ts +5 -1
  79. package/dist/components/NumberField.js +6 -4
  80. package/dist/components/Page.js +6 -6
  81. package/dist/components/Popover.js +2 -2
  82. package/dist/components/PreviewFrame.js +8 -8
  83. package/dist/components/RadioGroup.js +10 -10
  84. package/dist/components/Resizable.js +1 -1
  85. package/dist/components/SearchField.js +8 -8
  86. package/dist/components/Select.d.ts +13 -10
  87. package/dist/components/Select.js +18 -32
  88. package/dist/components/Sheet.js +6 -6
  89. package/dist/components/Sidebar.js +5 -5
  90. package/dist/components/SortableList.js +12 -12
  91. package/dist/components/Spinner.js +4 -4
  92. package/dist/components/Stack.d.ts +20 -0
  93. package/dist/components/Stack.js +45 -0
  94. package/dist/components/Surface.js +1 -1
  95. package/dist/components/Switch.js +5 -5
  96. package/dist/components/Table.d.ts +11 -1
  97. package/dist/components/Table.js +81 -53
  98. package/dist/components/Tabs.js +6 -6
  99. package/dist/components/TagGroup.js +10 -10
  100. package/dist/components/TextField.d.ts +7 -1
  101. package/dist/components/TextField.js +20 -11
  102. package/dist/components/TimeField.d.ts +6 -1
  103. package/dist/components/TimeField.js +51 -26
  104. package/dist/components/Timestamp.js +1 -1
  105. package/dist/components/ToggleGroup.js +2 -2
  106. package/dist/components/Toolbar.js +3 -3
  107. package/dist/components/Tooltip.js +5 -4
  108. package/dist/components/Tree.js +27 -27
  109. package/dist/components/internal/DateControl.js +11 -11
  110. package/dist/components/internal/FormScope.d.ts +2 -0
  111. package/dist/components/internal/FormScope.js +8 -0
  112. package/dist/components/internal/ListBoxGroup.d.ts +12 -0
  113. package/dist/components/internal/ListBoxGroup.js +46 -0
  114. package/dist/components/internal/ListBoxOption.d.ts +2 -2
  115. package/dist/components/internal/ListBoxOption.js +5 -5
  116. package/dist/components/internal/Locale.js +2 -2
  117. package/dist/components/internal/PopoverSurface.js +5 -4
  118. package/dist/components/internal/SelectionCheckbox.js +5 -5
  119. package/dist/components/internal/Slot.js +1 -1
  120. package/dist/components/internal/ToggleButton.js +4 -4
  121. package/dist/components/internal/Trigger.js +2 -2
  122. package/dist/components/internal/useDragDrop.js +2 -2
  123. package/dist/components/types.d.ts +4 -0
  124. package/dist/components.d.ts +3 -0
  125. package/dist/components.js +3 -0
  126. package/dist/core/Client.js +12 -1
  127. package/dist/core/Config.d.ts +6 -0
  128. package/dist/core/Config.js +12 -0
  129. package/dist/core/DashboardEntry.d.ts +19 -0
  130. package/dist/core/DashboardEntry.js +0 -0
  131. package/dist/core/Document.d.ts +2 -2
  132. package/dist/core/Document.js +10 -3
  133. package/dist/core/EntryFields.d.ts +8 -2
  134. package/dist/core/Field.d.ts +5 -0
  135. package/dist/core/Filter.d.ts +19 -2
  136. package/dist/core/Filter.js +5 -0
  137. package/dist/core/Graph.d.ts +10 -2
  138. package/dist/core/Overview.d.ts +74 -5
  139. package/dist/core/Overview.js +1 -0
  140. package/dist/core/Root.js +1 -1
  141. package/dist/core/Scope.d.ts +5 -0
  142. package/dist/core/Scope.js +22 -5
  143. package/dist/core/Type.d.ts +25 -6
  144. package/dist/core/Type.js +25 -7
  145. package/dist/core/db/LocalStore.d.ts +7 -0
  146. package/dist/core/field/CreateField.d.ts +1 -2
  147. package/dist/core/source/Source.d.ts +2 -0
  148. package/dist/core/source/Source.js +11 -0
  149. package/dist/core/util/Async.d.ts +2 -0
  150. package/dist/core/util/Async.js +12 -1
  151. package/dist/core/util/Checks.js +1 -1
  152. package/dist/core/util/Slugs.js +4 -3
  153. package/dist/dashboard/App.css +13 -11
  154. package/dist/dashboard/App.d.ts +1 -0
  155. package/dist/dashboard/App.js +22 -6
  156. package/dist/dashboard/Href.d.ts +37 -0
  157. package/dist/dashboard/Href.js +46 -0
  158. package/dist/dashboard/app/ActivityStatus.js +34 -12
  159. package/dist/dashboard/app/AuthView.js +2 -2
  160. package/dist/dashboard/app/BlockSettings.d.ts +9 -0
  161. package/dist/dashboard/app/BlockSettings.js +16 -0
  162. package/dist/dashboard/app/BlockSheet.js +3 -3
  163. package/dist/dashboard/app/BlockSheetStoryFrame.js +2 -2
  164. package/dist/dashboard/app/CompactField.js +15 -15
  165. package/dist/dashboard/app/CroppedImage.js +2 -2
  166. package/dist/dashboard/app/DashboardLayout.d.ts +1 -1
  167. package/dist/dashboard/app/DashboardLayout.js +12 -0
  168. package/dist/dashboard/app/EntryActions.d.ts +11 -0
  169. package/dist/dashboard/app/EntryActions.js +30 -0
  170. package/dist/dashboard/app/EntryFields.d.ts +15 -2
  171. package/dist/dashboard/app/EntryFields.js +26 -5
  172. package/dist/dashboard/app/EntryHeader.js +17 -7
  173. package/dist/dashboard/app/EntryReferences.js +2 -2
  174. package/dist/dashboard/app/EntrySidebar.js +25 -23
  175. package/dist/dashboard/app/EntrySidebarPreview.js +2 -2
  176. package/dist/dashboard/app/EntryValidationModal.js +3 -3
  177. package/dist/dashboard/app/Explorer.js +84 -45
  178. package/dist/dashboard/app/ExplorerBatchActions.js +4 -4
  179. package/dist/dashboard/app/ExplorerControls.js +5 -5
  180. package/dist/dashboard/app/ExplorerList.js +2 -2
  181. package/dist/dashboard/app/ExplorerModal.js +3 -3
  182. package/dist/dashboard/app/ExplorerOverviewHeader.d.ts +6 -0
  183. package/dist/dashboard/app/ExplorerOverviewHeader.js +28 -0
  184. package/dist/dashboard/app/ExplorerTable.js +7 -7
  185. package/dist/dashboard/app/ExplorerTabs.d.ts +12 -0
  186. package/dist/dashboard/app/ExplorerTabs.js +42 -0
  187. package/dist/dashboard/app/ImageEditor.js +31 -31
  188. package/dist/dashboard/app/LinkPicker.js +3 -3
  189. package/dist/dashboard/app/LocaleMenu.js +9 -9
  190. package/dist/dashboard/app/MediaPreviewCell.js +5 -5
  191. package/dist/dashboard/app/OverviewCell.js +12 -10
  192. package/dist/dashboard/app/RemoveDialog.js +5 -0
  193. package/dist/dashboard/app/SearchBar.js +4 -4
  194. package/dist/dashboard/app/SettingsTab.d.ts +6 -0
  195. package/dist/dashboard/app/SettingsTab.js +27 -0
  196. package/dist/dashboard/app/SidebarTree.js +19 -19
  197. package/dist/dashboard/app/WorkspaceMenu.js +6 -6
  198. package/dist/dashboard/app/WorkspaceRoots.js +102 -98
  199. package/dist/dashboard/app/editor/FileEditor.js +18 -18
  200. package/dist/dashboard/app/pages/EntryPage.js +42 -39
  201. package/dist/dashboard/app/pages/RootPage.js +3 -3
  202. package/dist/dashboard/app/pages/SplashPage.js +94 -79
  203. package/dist/dashboard/app/pages/UsersPage.js +9 -9
  204. package/dist/dashboard/app/ui/DashboardModal.js +14 -14
  205. package/dist/dashboard/atoms/activity.d.ts +1 -0
  206. package/dist/dashboard/atoms/activity.js +8 -5
  207. package/dist/dashboard/atoms/config.d.ts +2 -0
  208. package/dist/dashboard/atoms/dashboard.d.ts +11 -0
  209. package/dist/dashboard/atoms/dashboard.js +8 -0
  210. package/dist/dashboard/atoms/editor.d.ts +7 -1
  211. package/dist/dashboard/atoms/editor.js +14 -2
  212. package/dist/dashboard/atoms/explorer.d.ts +15 -0
  213. package/dist/dashboard/atoms/explorer.js +43 -14
  214. package/dist/dashboard/atoms/graph.d.ts +5 -0
  215. package/dist/dashboard/atoms/graph.js +26 -1
  216. package/dist/dashboard/atoms/nav.d.ts +16 -10
  217. package/dist/dashboard/atoms/nav.js +96 -36
  218. package/dist/dashboard/atoms/overview.d.ts +26 -4
  219. package/dist/dashboard/atoms/overview.js +41 -12
  220. package/dist/dashboard/atoms/root.js +7 -1
  221. package/dist/dashboard/boot/ActivityEvent.d.ts +1 -1
  222. package/dist/dashboard/boot/Boot.js +6 -0
  223. package/dist/dashboard/boot/DashboardWorker.d.ts +6 -0
  224. package/dist/dashboard/boot/DashboardWorker.js +89 -8
  225. package/dist/dashboard/boot/LoadWorker.js +3 -0
  226. package/dist/dashboard/boot/SyncEvent.d.ts +7 -0
  227. package/dist/dashboard/boot/SyncEvent.js +14 -0
  228. package/dist/dashboard/boot/WorkerDB.d.ts +1 -0
  229. package/dist/dashboard/boot/WorkerDB.js +3 -0
  230. package/dist/dashboard/cms/EntryTable.d.ts +4 -3
  231. package/dist/dashboard/cms/EntryTable.js +2 -2
  232. package/dist/dashboard/cms/ViewProps.d.ts +3 -2
  233. package/dist/dashboard/cms/hooks.d.ts +34 -18
  234. package/dist/dashboard/cms/hooks.js +43 -18
  235. package/dist/dashboard/contexts.d.ts +3 -2
  236. package/dist/dashboard/contexts.js +3 -2
  237. package/dist/dashboard/hook/UseEntryEditor.d.ts +1 -1
  238. package/dist/dashboard/hook/UseEntryEditor.js +7 -3
  239. package/dist/dashboard/icons.d.ts +1 -0
  240. package/dist/dashboard/icons.js +15 -0
  241. package/dist/database/BrowserEntryStore.js +35 -13
  242. package/dist/database/DatabaseTables.js +1 -1
  243. package/dist/database/EntryDatabase.d.ts +10 -1
  244. package/dist/database/EntryDatabase.js +31 -10
  245. package/dist/database/EntryStore.js +1 -11
  246. package/dist/database/entry/EntryTable.d.ts +5 -3
  247. package/dist/database/entry/EntryTable.js +25 -8
  248. package/dist/database/query/Condition.d.ts +4 -1
  249. package/dist/database/query/Condition.js +16 -5
  250. package/dist/database/query/EntryQuery.js +59 -7
  251. package/dist/database/query/Relation.d.ts +13 -1
  252. package/dist/database/query/Relation.js +19 -1
  253. package/dist/database/query/Search.d.ts +7 -1
  254. package/dist/database/query/Search.js +54 -9
  255. package/dist/database/sync/EntrySyncer.d.ts +2 -0
  256. package/dist/database/sync/EntrySyncer.js +4 -2
  257. package/dist/database/sync/Ingest.d.ts +2 -1
  258. package/dist/database/sync/Ingest.js +20 -6
  259. package/dist/field/code/CodeField.view.js +12 -12
  260. package/dist/field/link/EntryLink.d.ts +4 -5
  261. package/dist/field/link/FileLink.d.ts +2 -2
  262. package/dist/field/link/ImageLink.d.ts +2 -5
  263. package/dist/field/link/ImageLink.js +0 -1
  264. package/dist/field/link/LinkField.view.js +18 -15
  265. package/dist/field/list/ListField.view.js +50 -35
  266. package/dist/field/metadata/MetadataField.view.js +14 -14
  267. package/dist/field/richtext/RichTextBlock.js +39 -35
  268. package/dist/field/richtext/RichTextExtensions.js +23 -23
  269. package/dist/field/richtext/RichTextField.view.js +6 -6
  270. package/dist/field/richtext/RichTextInsertMenu.js +5 -5
  271. package/dist/field/richtext/RichTextToolbar.js +26 -1
  272. package/dist/field/richtext/Toolbar.d.ts +9 -1
  273. package/dist/field/richtext/Toolbar.js +12 -2
  274. package/dist/field/select/SelectField.view.js +1 -0
  275. package/dist/index.css +174 -23
  276. package/dist/index.d.ts +1 -0
  277. package/dist/index.js +3 -1
  278. package/dist/next.d.ts +2 -0
  279. package/dist/next.edge.d.ts +2 -0
  280. package/dist/picker/entry/EntryPicker.d.ts +3 -1
  281. package/dist/picker/entry/EntryPicker.js +15 -1
  282. package/dist/preview/react.js +3 -0
  283. package/dist/query.d.ts +26 -14
  284. package/dist/query.js +4 -0
  285. package/dist/types.d.ts +8 -5
  286. package/dist/ui/Stack.d.ts +4 -2
  287. package/docs/ai-setup.md +3 -3
  288. package/docs/cli.md +4 -2
  289. package/docs/components/combo-box.md +105 -16
  290. package/docs/components/date-field.md +1 -1
  291. package/docs/components/dialog.md +51 -0
  292. package/docs/components/form.md +67 -0
  293. package/docs/components/link.md +10 -0
  294. package/docs/components/multiple-select.md +9 -17
  295. package/docs/components/number-field.md +5 -1
  296. package/docs/components/select.md +9 -20
  297. package/docs/components/stack.md +42 -0
  298. package/docs/components/table.md +83 -0
  299. package/docs/components/text-field.md +9 -1
  300. package/docs/components/time-field.md +5 -1
  301. package/docs/components.md +39 -1
  302. package/docs/configuration.md +3 -1
  303. package/docs/custom-fields.md +135 -7
  304. package/docs/deploy/self-host.md +2 -0
  305. package/docs/deploy.md +7 -4
  306. package/docs/fields/image.md +0 -1
  307. package/docs/fields/list.md +19 -0
  308. package/docs/fields/object.md +5 -3
  309. package/docs/fields/rich-text.md +31 -0
  310. package/docs/index.md +8 -6
  311. package/docs/instant-publishing.md +23 -2
  312. package/docs/overviews.md +128 -4
  313. package/docs/query/filtering.md +2 -0
  314. package/docs/query/related.md +58 -9
  315. package/docs/query.md +52 -1
  316. package/docs/quickstart.md +5 -5
  317. package/docs/schema/document.md +2 -0
  318. package/docs/schema/type.md +3 -1
  319. package/docs/upgrading.md +100 -19
  320. package/llms-full.txt +1022 -125
  321. package/package.json +1 -1
  322. package/readme.md +2 -2
  323. package/dist/chunks/{chunk-4ZA6KZHS.js → chunk-6FQ5IMNA.js} +3 -3
@@ -1,5 +1,5 @@
1
1
  {
2
- "_id": "3Kd9mGx2n7q2f3qhMUmFsgaYVyu",
2
+ "_id": "3Kg2pCrguJIYIFElVMkyXOQsdZa",
3
3
  "_type": "Page",
4
4
  "_index": "a0",
5
5
  "_seeded": "welcome.json",
@@ -1 +1 @@
1
- {"url":"http://localhost:1","pid":3062}
1
+ {"url":"http://localhost:1","pid":3063}
@@ -1 +1 @@
1
- {"url":"http://localhost:36863","pid":3062}
1
+ {"url":"http://localhost:34865","pid":3063}
@@ -9,7 +9,7 @@ import {
9
9
  decodePreviewRequest
10
10
  } from "alinea/backend/resolver/ParsePreview";
11
11
  import { dashboardUrl } from "./dashboardUrl.js";
12
- import { createThrottledSync } from "alinea/backend/util/Syncable";
12
+ import { createThrottledSync, syncWait } from "alinea/backend/util/Syncable";
13
13
  import { Client } from "alinea/core/Client";
14
14
  import { CMS } from "alinea/core/CMS";
15
15
  import {
@@ -18,6 +18,7 @@ import {
18
18
  import { outcome } from "alinea/core/Outcome";
19
19
  import { getScope } from "alinea/core/Scope";
20
20
  import { trace } from "alinea/core/Trace";
21
+ import { within } from "alinea/core/util/Async";
21
22
  import { getPreviewPayloadFromCookies } from "alinea/preview/PreviewCookies";
22
23
  import { Headers } from "@alinea/iso";
23
24
  import { cache } from "react";
@@ -99,7 +100,10 @@ var NextCMS = class extends CMS {
99
100
  (row) => timed(row, () => this.#syncDb(db, client, void 0))
100
101
  );
101
102
  try {
102
- const sha = await db.syncWith(client, preValidatedRemote);
103
+ const sha = await within(
104
+ db.syncWith(client, preValidatedRemote),
105
+ syncWait
106
+ );
103
107
  this.#syncedAt = Date.now();
104
108
  return sha;
105
109
  } catch (error) {
@@ -15,13 +15,10 @@ async function requestContext(config, request) {
15
15
  applyAuth: (init) => forwardDevelopmentCredentials(request, apiKey, init)
16
16
  };
17
17
  }
18
- const nodeEnv = process.env.NODE_ENV;
19
- const baseUrl = Config.baseUrl(config, nodeEnv);
20
- if (!baseUrl) throw new Error(`Missing baseUrl in config for ${nodeEnv}`);
21
18
  const localOrigin = process.env.__NEXT_PRIVATE_ORIGIN;
22
19
  return {
23
20
  isDev: false,
24
- handlerUrl: new URL(Config.handlerUrl(config), baseUrl),
21
+ handlerUrl: Config.serverHandlerUrl(config),
25
22
  apiKey,
26
23
  publicUrl: localOrigin ? new URL(localOrigin) : void 0
27
24
  };
@@ -10,6 +10,7 @@ import {
10
10
  import {
11
11
  createHandler as createCoreHandler
12
12
  } from "alinea/backend/Handler";
13
+ import { committedEntries, removedEntries } from "alinea/backend/CommitEntries";
13
14
  import { HandleAction } from "alinea/backend/HandleAction";
14
15
  import { JWTPreviews } from "alinea/backend/util/JWTPreviews";
15
16
  import { CloudRemote } from "alinea/cloud/CloudRemote";
@@ -79,18 +80,28 @@ function createHandlerWithDatabase(input, openGeneratedDatabase) {
79
80
  });
80
81
  let sha;
81
82
  let user;
83
+ let before = [];
82
84
  try {
83
85
  user = await devServer.user();
84
86
  if (!user) return failure(401, "Unauthorized");
85
87
  const adjusted = await options.beforeCommit?.({ mutations, user });
86
88
  if (adjusted) mutations = [...adjusted];
89
+ if (options.afterCommit)
90
+ before = await committedEntries(devServer, mutations).catch(() => []);
87
91
  ({ sha } = await devServer.mutate(mutations));
88
92
  } catch (error) {
89
93
  if (error instanceof HttpError) return failure(error.code, error.message);
90
94
  throw error;
91
95
  }
92
96
  try {
93
- await afterCommit({ mutations, sha, user });
97
+ const entries = options.afterCommit ? await committedEntries(devServer, mutations) : [];
98
+ await afterCommit({
99
+ mutations,
100
+ sha,
101
+ user,
102
+ entries,
103
+ removed: removedEntries(before, entries)
104
+ });
94
105
  } catch (error) {
95
106
  console.error("Alinea afterCommit hook failed", error);
96
107
  }
@@ -0,0 +1,17 @@
1
+ import type { DashboardEntry } from 'alinea/core/DashboardEntry';
2
+ import type { Mutation } from 'alinea/core/db/Mutation';
3
+ import type { Resolver } from 'alinea/core/Resolver';
4
+ /** An entry that no longer exists after a commit */
5
+ export interface RemovedEntry {
6
+ id: string;
7
+ type: string;
8
+ locale: string | null;
9
+ /** The url the entry had before the commit */
10
+ url: string;
11
+ }
12
+ /**
13
+ * The latest version, in every locale, of the entries that mutations change
14
+ */
15
+ export declare function committedEntries(resolver: Resolver, mutations: ReadonlyArray<Mutation>): Promise<Array<DashboardEntry>>;
16
+ /** The entries of `before` that are not in `after` */
17
+ export declare function removedEntries(before: Array<DashboardEntry>, after: Array<DashboardEntry>): Array<RemovedEntry>;
@@ -0,0 +1,39 @@
1
+ import "../chunks/chunk-CQ52GEHY.js";
2
+
3
+ // src/backend/CommitEntries.ts
4
+ import { Entry } from "alinea/core/Entry";
5
+ var select = {
6
+ id: Entry.id,
7
+ type: Entry.type,
8
+ title: Entry.title,
9
+ path: Entry.path,
10
+ url: Entry.url,
11
+ locale: Entry.locale,
12
+ status: Entry.status,
13
+ workspace: Entry.workspace,
14
+ root: Entry.root,
15
+ parentId: Entry.parentId,
16
+ data: Entry.data
17
+ };
18
+ function entryIds(mutations) {
19
+ const ids = mutations.map((mutation) => "id" in mutation ? mutation.id : "");
20
+ return [...new Set(ids.filter(Boolean))];
21
+ }
22
+ async function committedEntries(resolver, mutations) {
23
+ const ids = entryIds(mutations);
24
+ if (ids.length === 0) return [];
25
+ return await resolver.resolve({
26
+ id: { in: ids },
27
+ status: "preferDraft",
28
+ select
29
+ });
30
+ }
31
+ function removedEntries(before, after) {
32
+ const key = (entry) => `${entry.id}:${entry.locale}`;
33
+ const remaining = new Set(after.map(key));
34
+ return before.filter((entry) => !remaining.has(key(entry))).map(({ id, type, locale, url }) => ({ id, type, locale, url }));
35
+ }
36
+ export {
37
+ committedEntries,
38
+ removedEntries
39
+ };
@@ -1,6 +1,8 @@
1
1
  import type { CMS } from 'alinea/core/CMS';
2
2
  import type { AuthedContext, RemoteConnection, RequestContext } from 'alinea/core/Connection';
3
3
  import type { LocalStore } from 'alinea/core/db/LocalStore';
4
+ import type { DashboardEntry } from 'alinea/core/DashboardEntry';
5
+ import { type RemovedEntry } from './CommitEntries.js';
4
6
  import type { Mutation } from 'alinea/core/db/Mutation';
5
7
  import type { User } from 'alinea/core/User';
6
8
  export interface Handler {
@@ -14,6 +16,13 @@ export interface BeforeCommitContext {
14
16
  }
15
17
  export interface AfterCommitContext extends BeforeCommitContext {
16
18
  sha: string;
19
+ /**
20
+ * The entries the mutations changed as they are after the commit: the
21
+ * latest version in each locale
22
+ */
23
+ entries: Array<DashboardEntry>;
24
+ /** Entries that no longer exist after the commit, with the url they had */
25
+ removed: Array<RemovedEntry>;
17
26
  }
18
27
  export interface HandlerHooks {
19
28
  beforeCommit?(context: BeforeCommitContext): HookResponse<ReadonlyArray<Mutation>>;
@@ -4,7 +4,7 @@ import {
4
4
  object,
5
5
  optional,
6
6
  string
7
- } from "../chunks/chunk-UPY2RUZF.js";
7
+ } from "../chunks/chunk-TWBREZWC.js";
8
8
  import {
9
9
  PLazy
10
10
  } from "../chunks/chunk-IKINPSS5.js";
@@ -19,6 +19,10 @@ import {
19
19
  } from "alinea/core/BlobTransport";
20
20
  import { Config } from "alinea/core/Config";
21
21
  import { developmentKeyHeader } from "alinea/core/Connection";
22
+ import {
23
+ committedEntries,
24
+ removedEntries
25
+ } from "./CommitEntries.js";
22
26
  import { Entry } from "alinea/core/Entry";
23
27
  import { ErrorCode, HttpError } from "alinea/core/HttpError";
24
28
  import { MediaLocation } from "alinea/core/media/MediaLocation";
@@ -27,15 +31,18 @@ import { assertUploadSize } from "alinea/core/media/UploadLimits";
27
31
  import { Permission, Policy } from "alinea/core/Role";
28
32
  import { getScope } from "alinea/core/Scope";
29
33
  import { ShaMismatchError } from "alinea/core/source/ShaMismatchError";
34
+ import { sourceAtTree } from "alinea/core/source/Source";
30
35
  import { base64 } from "alinea/core/util/Encoding";
36
+ import { within } from "alinea/core/util/Async";
31
37
  import { isRecord } from "alinea/core/util/Objects";
32
- import { isAbsolute, normalize } from "alinea/core/util/Paths";
38
+ import { fileVersions } from "alinea/core/util/EntryFilenames";
39
+ import { isAbsolute, normalize, relative } from "alinea/core/util/Paths";
33
40
  import { InvalidCredentialsError, MissingCredentialsError } from "./Auth.js";
34
41
  import { HandleAction } from "./HandleAction.js";
35
42
  import { proxyMediaUrl } from "./api/ProxyMedia.js";
36
43
  import { applyPreview, decodePreviewRequest } from "./resolver/ParsePreview.js";
37
44
  import { compressResponse } from "./router/Router.js";
38
- import { createThrottledSync } from "./util/Syncable.js";
45
+ import { createThrottledSync, syncWait } from "./util/Syncable.js";
39
46
  var PrepareBody = object({
40
47
  filename: string,
41
48
  size: optional(number)
@@ -53,9 +60,9 @@ function createHandler({
53
60
  const local = await db;
54
61
  const simulateLatency = process.env.ALINEA_LATENCY;
55
62
  if (simulateLatency) await new Promise((resolve) => setTimeout(resolve, 2e3));
56
- async function syncForRead(cnx) {
63
+ async function syncForRead(sync) {
57
64
  try {
58
- await local.syncWith(cnx);
65
+ await within(sync, syncWait);
59
66
  } catch (error) {
60
67
  console.warn(
61
68
  `Alinea could not sync with the remote, serving current content: ${error instanceof Error ? error.message : String(error)}`
@@ -64,7 +71,7 @@ function createHandler({
64
71
  }
65
72
  async function periodicSync(cnx, syncInterval) {
66
73
  if (dev) return;
67
- return throttle(() => syncForRead(cnx), syncInterval);
74
+ return syncForRead(throttle(() => local.syncWith(cnx), syncInterval));
68
75
  }
69
76
  try {
70
77
  const previews = new JWTPreviews(context.apiKey);
@@ -258,7 +265,7 @@ function createHandler({
258
265
  } else {
259
266
  const preview = await decodePreviewRequest(query.preview);
260
267
  if ("contentHash" in preview && await local.sha !== preview.contentHash)
261
- await syncForRead(cnx);
268
+ await syncForRead(local.syncWith(cnx));
262
269
  query.preview = await applyPreview(local, preview);
263
270
  }
264
271
  return Response.json(await local.resolve(query) ?? null);
@@ -278,59 +285,90 @@ function createHandler({
278
285
  user: user.claims
279
286
  });
280
287
  if (adjusted) mutations = adjusted;
288
+ const before = hooks.afterCommit ? await committedEntries(local, mutations) : [];
281
289
  const attempt = async (retry = 0) => {
282
290
  if (retry > 0) await local.syncWith(cnx);
283
291
  const request2 = {
284
292
  ...await local.request(mutations, policy),
285
293
  user: user.claims
286
294
  };
295
+ let sha2;
287
296
  try {
288
- let { sha: sha2 } = await cnx.write(request2);
289
- if (sha2 === request2.intoSha) {
290
- await local.write(request2);
291
- } else {
292
- sha2 = await local.syncWith(cnx);
293
- }
294
- return sha2;
297
+ sha2 = (await cnx.write(request2)).sha;
295
298
  } catch (error) {
296
299
  const isConflict = error instanceof ShaMismatchError || error instanceof HttpError && error.code === 409 || error instanceof Response && error.status === 409;
297
300
  if (isConflict && retry < 3) return attempt(retry + 1);
298
301
  throw error;
299
302
  }
303
+ try {
304
+ if (sha2 === request2.intoSha) await local.write(request2);
305
+ else sha2 = await local.syncWith(cnx);
306
+ } catch {
307
+ sha2 = await local.syncWith(cnx).catch(() => sha2);
308
+ }
309
+ return sha2;
300
310
  };
301
311
  const sha = await attempt();
302
312
  try {
303
- await hooks.afterCommit?.({ mutations, sha, user: user.claims });
313
+ if (hooks.afterCommit) {
314
+ const entries = await committedEntries(local, mutations);
315
+ await hooks.afterCommit({
316
+ mutations,
317
+ sha,
318
+ user: user.claims,
319
+ entries,
320
+ removed: removedEntries(before, entries)
321
+ });
322
+ }
304
323
  } catch (error) {
305
324
  console.error("Alinea afterCommit hook failed", error);
306
325
  }
307
326
  return Response.json({ sha });
308
327
  }
309
328
  if (action === HandleAction.History && request.method === "GET") {
310
- expectUser();
329
+ const user = expectUser();
311
330
  expectJson();
312
331
  const file = string(url.searchParams.get("file"));
313
332
  const revisionId = string.nullable(url.searchParams.get("revisionId"));
314
- const result = await (revisionId ? cnx.revisionData(file, revisionId) : cnx.revisions(file));
333
+ const record = revisionId ? await cnx.revisionData(file, revisionId) : void 0;
334
+ const contentDir = Config.contentDir(cms.config);
335
+ const owners = () => historyEntries(local, contentDir, file, record);
336
+ let entries = await owners();
337
+ if (entries.length === 0) {
338
+ await syncForRead(local.syncWith(cnx));
339
+ entries = await owners();
340
+ }
341
+ if (entries.length === 0)
342
+ throw new HttpError(ErrorCode.NotFound, "Entry not found");
343
+ const policy = await user.policy;
344
+ for (const entry of entries) policy.assert(Permission.Read, entry);
345
+ const result = revisionId ? record : await cnx.revisions(file);
315
346
  return Response.json(result ?? null);
316
347
  }
317
348
  if (action === HandleAction.Tree && request.method === "GET") {
318
349
  expectJson();
319
350
  const sha = string(url.searchParams.get("sha"));
320
- await syncForRead(cnx);
351
+ let source = cnx;
352
+ try {
353
+ const remoteTree = await cnx.getTreeIfDifferent(sha);
354
+ if (!remoteTree) return Response.json(null);
355
+ source = sourceAtTree(cnx, remoteTree);
356
+ } catch {
357
+ }
358
+ await syncForRead(local.syncWith(source));
321
359
  const tree = await local.getTreeIfDifferent(sha);
322
360
  return compressResponse(request, Response.json(tree ?? null));
323
361
  }
324
362
  if (action === HandleAction.Sha && request.method === "GET") {
325
363
  expectJson();
326
- await syncForRead(cnx);
364
+ await syncForRead(local.syncWith(cnx));
327
365
  return Response.json(await local.sha);
328
366
  }
329
367
  if (action === HandleAction.Blob && request.method === "POST") {
330
368
  const { shas } = object({ shas: array(string) })(await body);
331
369
  let tree = await local.source.getTree();
332
370
  if (!dev && shas.some((sha) => !tree.hasSha(sha))) {
333
- await syncForRead(cnx);
371
+ await syncForRead(local.syncWith(cnx));
334
372
  tree = await local.source.getTree();
335
373
  }
336
374
  const fromLocal = [];
@@ -421,6 +459,28 @@ function createHandler({
421
459
  }
422
460
  };
423
461
  }
462
+ var historyResource = {
463
+ id: Entry.id,
464
+ type: Entry.type,
465
+ workspace: Entry.workspace,
466
+ root: Entry.root,
467
+ parents: Entry.parents,
468
+ locale: Entry.locale
469
+ };
470
+ async function historyEntries(local, contentDir, file, record) {
471
+ const filePath = relative(contentDir, file);
472
+ if (filePath.startsWith("../")) return [];
473
+ const status = "all";
474
+ const select = historyResource;
475
+ const atFile = await local.find({
476
+ filePath: { in: fileVersions(filePath) },
477
+ status,
478
+ select
479
+ });
480
+ const id = record?._id;
481
+ if (atFile.length > 0 || typeof id !== "string") return atFile;
482
+ return local.find({ id, status, select });
483
+ }
424
484
  function redirectFile(location, cacheControl) {
425
485
  return new Response(null, {
426
486
  status: 307,
@@ -42,7 +42,11 @@ var BasicAuth = class {
42
42
  const [scheme, token] = auth.split(" ", 2);
43
43
  if (scheme !== "Basic")
44
44
  throw new MissingCredentialsError("Invalid authorization scheme");
45
- const [username, password] = atob(token).split(":");
45
+ const credentials = atob(token);
46
+ const colon = credentials.indexOf(":");
47
+ if (colon === -1) throw new InvalidCredentialsError("Invalid credentials");
48
+ const username = credentials.slice(0, colon);
49
+ const password = credentials.slice(colon + 1);
46
50
  const authorized = await this.#verify(username, password);
47
51
  if (!authorized) throw new InvalidCredentialsError("Invalid credentials");
48
52
  const user = typeof authorized === "boolean" ? {
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  driver_exports
3
- } from "../../chunks/chunk-4ZA6KZHS.js";
4
- import "../../chunks/chunk-VA6NC3MI.js";
3
+ } from "../../chunks/chunk-6FQ5IMNA.js";
5
4
  import "../../chunks/chunk-A2I6MCCS.js";
5
+ import "../../chunks/chunk-VA6NC3MI.js";
6
6
  import "../../chunks/chunk-RAR44T5O.js";
7
7
  import "../../chunks/chunk-PTYZMV5G.js";
8
8
  import "../../chunks/chunk-N47Y62CI.js";
@@ -2,12 +2,11 @@ import {
2
2
  include
3
3
  } from "../../chunks/chunk-XTWYBWP2.js";
4
4
  import {
5
- blob,
6
5
  id,
7
6
  integer,
8
7
  text,
9
8
  varchar
10
- } from "../../chunks/chunk-5H4D5JQL.js";
9
+ } from "../../chunks/chunk-2PP7WJUJ.js";
11
10
  import {
12
11
  Builder
13
12
  } from "../../chunks/chunk-2BJ2IFQM.js";
@@ -43,11 +42,11 @@ import { slugify } from "alinea/core/util/Slugs";
43
42
  import { HandleAction } from "../HandleAction.js";
44
43
  var UploadTable = table("alinea_upload", {
45
44
  entryId: varchar(void 0, { length: 27 }).primaryKey(),
46
- content: blob().notNull()
45
+ content: largeBlob().notNull()
47
46
  });
48
47
  var UserTable = table("alinea_user", {
49
48
  id: id(),
50
- email: text().notNull().unique(),
49
+ email: indexedText().notNull().unique(),
51
50
  name: text()
52
51
  });
53
52
  var UserRoleTable = table("alinea_user_role", {
@@ -61,7 +60,9 @@ var BlobTable = table("alinea_blob", {
61
60
  usedAt: integer().notNull()
62
61
  });
63
62
  var tables = [UploadTable, UserTable, UserRoleTable, BlobTable];
63
+ var maxParams = 100;
64
64
  var blobsPerStatement = 50;
65
+ var blobsPerInsert = Math.floor(maxParams / 3);
65
66
  var day = 24 * 60 * 60;
66
67
  var blobRetention = 30 * day;
67
68
  var blobCleanups = /* @__PURE__ */ new WeakMap();
@@ -141,7 +142,7 @@ var DatabaseApi = class {
141
142
  const db = await this.#db;
142
143
  const now = seconds();
143
144
  const unique = [...new Map(blobs)];
144
- for (const batch of chunks(unique, blobsPerStatement)) {
145
+ for (const batch of chunks(unique, blobsPerInsert)) {
145
146
  const shas = batch.map(([sha]) => sha);
146
147
  const stored = new Set(
147
148
  await db.select(BlobTable.sha).from(BlobTable).where(inArray(BlobTable.sha, shas))
@@ -252,6 +253,18 @@ function largeBlob() {
252
253
  }
253
254
  });
254
255
  }
256
+ function indexedText() {
257
+ return new Column({
258
+ type: new ColumnType(
259
+ "text",
260
+ [],
261
+ sql.universal({
262
+ mysql: sql`varchar(255)`,
263
+ default: sql`text`
264
+ })
265
+ )
266
+ });
267
+ }
255
268
  async function renewBlobs(db, shas, now) {
256
269
  if (shas.length === 0) return;
257
270
  await db.update(BlobTable).set({ usedAt: now }).where(and(inArray(BlobTable.sha, shas), lt(BlobTable.usedAt, now - day)));
@@ -3,6 +3,19 @@ import type { EntryRecord } from 'alinea/core/EntryRecord';
3
3
  import type { CommitRequest } from 'alinea/core/db/CommitRequest';
4
4
  import { GithubSource, type GithubSourceOptions } from 'alinea/core/source/GithubSource';
5
5
  export interface GithubOptions extends GithubSourceOptions {
6
+ /**
7
+ * Commit as this author instead of the account that owns the token. Such
8
+ * commits are written through the REST API, which GraphQL commits do not
9
+ * allow, so GitHub does not mark them as verified. A save usually spends
10
+ * three REST requests, plus one per uploaded file. Removing a file that is
11
+ * already gone costs one more tree request, and a branch that moved in
12
+ * the meantime repeats the save.
13
+ */
14
+ author?: GithubCommitAuthor;
15
+ }
16
+ export interface GithubCommitAuthor {
17
+ name: string;
18
+ email: string;
6
19
  }
7
20
  export declare class GithubApi extends GithubSource implements HistoryApi, CommitApi, SyncApi {
8
21
  #private;