alinea 2.0.0-preview.12 → 2.0.0-preview.13

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 (264) 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 +16 -8
  5. package/dist/adapter/next/dashboardUrl.d.ts +6 -0
  6. package/dist/adapter/next/dashboardUrl.js +13 -0
  7. package/dist/adapter/next/syncCheck.d.ts +15 -2
  8. package/dist/adapter/next/syncCheck.js +26 -9
  9. package/dist/backend/Handler.js +5 -1
  10. package/dist/backend/api/BlobStore.d.ts +21 -0
  11. package/dist/backend/api/BlobStore.js +63 -0
  12. package/dist/backend/api/CreateBackend.js +10 -8
  13. package/dist/backend/api/DatabaseApi.d.ts +4 -1
  14. package/dist/backend/api/DatabaseApi.js +88 -4
  15. package/dist/backend/api/GithubApi.js +47 -24
  16. package/dist/backend/api/database.js +6 -6
  17. package/dist/bundled.d.ts +676 -280
  18. package/dist/chunks/{bun-sqlite-5SHXN3AT.js → bun-sqlite-RI6BYU5S.js} +3 -3
  19. package/dist/chunks/{chunk-OHGAVV4G.js → chunk-3QFSGTGF.js} +2 -3
  20. package/dist/chunks/{chunk-QJGGYF6E.js → chunk-4ZA6KZHS.js} +5 -5
  21. package/dist/chunks/{chunk-ASYKC6IE.js → chunk-5H4D5JQL.js} +1 -1
  22. package/dist/chunks/{chunk-ETWOY4AL.js → chunk-A2I6MCCS.js} +1 -1
  23. package/dist/chunks/{chunk-W5AXN7GH.js → chunk-CYVOGRSD.js} +1 -2
  24. package/dist/chunks/{chunk-TIJTT5ZI.js → chunk-G43W7AME.js} +1 -0
  25. package/dist/chunks/{chunk-RE523BQ7.js → chunk-HOETF6HN.js} +5829 -5830
  26. package/dist/chunks/{chunk-AIU6CMVT.js → chunk-PTYZMV5G.js} +1 -1
  27. package/dist/chunks/{chunk-JOF663EG.js → chunk-QHHMKJUO.js} +1 -1
  28. package/dist/chunks/{chunk-66YZ47TY.js → chunk-RAR44T5O.js} +1 -1
  29. package/dist/chunks/{chunk-QSN6RH6X.js → chunk-VA6NC3MI.js} +1 -1
  30. package/dist/chunks/{node-sqlite-XVNTNMCY.js → node-sqlite-C7VJ2NSN.js} +3 -3
  31. package/dist/cli/Mcp.js +1 -1
  32. package/dist/cli/Serve.js +1 -1
  33. package/dist/cli/bin.js +2 -3
  34. package/dist/cli/serve/mcp/DevMcp.js +1 -1
  35. package/dist/cli/serve/mcp/McpTools.js +29 -1
  36. package/dist/cloud/CloudRemote.js +1 -1
  37. package/dist/components/Alert.js +2 -2
  38. package/dist/components/AppShell.js +2 -2
  39. package/dist/components/Badge.d.ts +3 -1
  40. package/dist/components/Badge.js +5 -3
  41. package/dist/components/Breadcrumbs.js +7 -7
  42. package/dist/components/Button.d.ts +6 -1
  43. package/dist/components/Button.js +16 -7
  44. package/dist/components/Calendar.js +20 -20
  45. package/dist/components/Checkbox.js +7 -7
  46. package/dist/components/CheckboxGroup.js +4 -4
  47. package/dist/components/Code.js +2 -2
  48. package/dist/components/Collapsible.js +3 -3
  49. package/dist/components/ColorSwatch.js +2 -2
  50. package/dist/components/ColorSwatchPicker.js +2 -2
  51. package/dist/components/ComboBox.js +7 -7
  52. package/dist/components/Command.js +14 -13
  53. package/dist/components/ContentCard.d.ts +3 -1
  54. package/dist/components/ContentCard.js +46 -24
  55. package/dist/components/ContentGrid.js +17 -17
  56. package/dist/components/DataList.js +2 -2
  57. package/dist/components/DateField.js +2 -2
  58. package/dist/components/DatePicker.js +2 -2
  59. package/dist/components/DateRangePicker.js +2 -2
  60. package/dist/components/Dialog.js +7 -7
  61. package/dist/components/DropZone.js +6 -6
  62. package/dist/components/DropdownMenu.js +9 -9
  63. package/dist/components/Empty.js +5 -5
  64. package/dist/components/Field.js +8 -8
  65. package/dist/components/List.js +27 -27
  66. package/dist/components/MediaPreview.js +5 -5
  67. package/dist/components/MultipleSelect.js +22 -22
  68. package/dist/components/NavRail.js +4 -4
  69. package/dist/components/NumberField.js +4 -4
  70. package/dist/components/Page.js +3 -3
  71. package/dist/components/Popover.js +2 -2
  72. package/dist/components/PreviewFrame.js +9 -9
  73. package/dist/components/RadioGroup.js +9 -9
  74. package/dist/components/Resizable.js +4 -4
  75. package/dist/components/SearchField.d.ts +6 -1
  76. package/dist/components/SearchField.js +5 -3
  77. package/dist/components/Select.js +13 -13
  78. package/dist/components/Sheet.d.ts +50 -0
  79. package/dist/components/Sheet.js +202 -0
  80. package/dist/components/Sidebar.js +4 -4
  81. package/dist/components/SortableList.d.ts +34 -30
  82. package/dist/components/SortableList.js +161 -264
  83. package/dist/components/Spinner.js +2 -2
  84. package/dist/components/Surface.js +2 -2
  85. package/dist/components/Switch.js +8 -8
  86. package/dist/components/Table.d.ts +3 -1
  87. package/dist/components/Table.js +57 -46
  88. package/dist/components/Tabs.js +5 -5
  89. package/dist/components/TagGroup.js +8 -8
  90. package/dist/components/TextField.js +10 -10
  91. package/dist/components/TimeField.js +6 -6
  92. package/dist/components/Timestamp.js +1 -1
  93. package/dist/components/ToggleGroup.js +2 -2
  94. package/dist/components/Toolbar.js +2 -2
  95. package/dist/components/Tooltip.js +5 -5
  96. package/dist/components/Tree.js +24 -24
  97. package/dist/components/internal/DateControl.js +12 -12
  98. package/dist/components/internal/ListBoxOption.js +6 -6
  99. package/dist/components/internal/Locale.js +2 -2
  100. package/dist/components/internal/PopoverSurface.js +4 -4
  101. package/dist/components/internal/SelectionCheckbox.js +5 -5
  102. package/dist/components/internal/Slot.js +1 -1
  103. package/dist/components/internal/ToggleButton.js +3 -3
  104. package/dist/components/internal/Trigger.js +2 -2
  105. package/dist/components/internal/sortableDrag.d.ts +19 -0
  106. package/dist/components/internal/sortableDrag.js +175 -0
  107. package/dist/components/internal/useDragDrop.js +2 -2
  108. package/dist/components.d.ts +1 -0
  109. package/dist/components.js +1 -0
  110. package/dist/core/Config.d.ts +6 -1
  111. package/dist/core/Document.d.ts +9 -1
  112. package/dist/core/Document.js +31 -6
  113. package/dist/core/Overview.d.ts +2 -4
  114. package/dist/core/TextDoc.d.ts +4 -0
  115. package/dist/core/Validation.d.ts +0 -1
  116. package/dist/core/Validation.js +0 -6
  117. package/dist/core/field/RichTextField.js +23 -4
  118. package/dist/core/media/MediaTypes.js +1 -1
  119. package/dist/core/source/GithubSource.d.ts +4 -0
  120. package/dist/core/source/GithubSource.js +127 -25
  121. package/dist/core/source/GithubUsage.d.ts +21 -0
  122. package/dist/core/source/GithubUsage.js +47 -0
  123. package/dist/dashboard/App.d.ts +2 -2
  124. package/dist/dashboard/App.js +5 -3
  125. package/dist/dashboard/ViewKeys.d.ts +1 -0
  126. package/dist/dashboard/ViewKeys.js +1 -0
  127. package/dist/dashboard/app/ActivityStatus.js +7 -7
  128. package/dist/dashboard/app/AuthView.js +5 -5
  129. package/dist/dashboard/app/BlockSheet.d.ts +50 -0
  130. package/dist/dashboard/app/BlockSheet.js +207 -0
  131. package/dist/dashboard/app/BlockSheetStoryFrame.d.ts +6 -0
  132. package/dist/dashboard/app/BlockSheetStoryFrame.js +29 -0
  133. package/dist/dashboard/app/CompactField.js +16 -16
  134. package/dist/dashboard/app/DashboardLayout.js +3 -1
  135. package/dist/dashboard/app/EntryFields.js +2 -2
  136. package/dist/dashboard/app/EntryHeader.js +33 -34
  137. package/dist/dashboard/app/EntryReferences.js +7 -7
  138. package/dist/dashboard/app/EntrySidebar.js +15 -15
  139. package/dist/dashboard/app/EntrySidebarPreview.js +3 -3
  140. package/dist/dashboard/app/EntryStatusIcon.d.ts +23 -0
  141. package/dist/dashboard/app/EntryStatusIcon.js +58 -0
  142. package/dist/dashboard/app/EntryValidationModal.d.ts +6 -5
  143. package/dist/dashboard/app/EntryValidationModal.js +20 -20
  144. package/dist/dashboard/app/Explorer.d.ts +3 -1
  145. package/dist/dashboard/app/Explorer.js +48 -44
  146. package/dist/dashboard/app/ExplorerBatchActions.d.ts +3 -1
  147. package/dist/dashboard/app/ExplorerBatchActions.js +68 -43
  148. package/dist/dashboard/app/ExplorerCards.js +7 -1
  149. package/dist/dashboard/app/ExplorerControls.js +6 -6
  150. package/dist/dashboard/app/ExplorerList.js +5 -5
  151. package/dist/dashboard/app/ExplorerModal.js +2 -2
  152. package/dist/dashboard/app/ExplorerTable.js +11 -8
  153. package/dist/dashboard/app/ImageEditor.js +28 -28
  154. package/dist/dashboard/app/ImagePicker.js +3 -13
  155. package/dist/dashboard/app/LinkPicker.js +28 -26
  156. package/dist/dashboard/app/LocaleMenu.js +4 -4
  157. package/dist/dashboard/app/MediaPreviewCell.js +1 -1
  158. package/dist/dashboard/app/MoveConfirmDialog.d.ts +5 -0
  159. package/dist/dashboard/app/MoveConfirmDialog.js +79 -0
  160. package/dist/dashboard/app/OverviewCell.js +8 -13
  161. package/dist/dashboard/app/PendingUploadsView.js +3 -3
  162. package/dist/dashboard/app/ReadOnlyBadge.d.ts +1 -1
  163. package/dist/dashboard/app/RemoveDialog.d.ts +17 -0
  164. package/dist/dashboard/app/{DeleteDialog.js → RemoveDialog.js} +67 -27
  165. package/dist/dashboard/app/RemoveRedirect.d.ts +7 -0
  166. package/dist/dashboard/app/RemoveRedirect.js +123 -0
  167. package/dist/dashboard/app/SearchBar.d.ts +9 -0
  168. package/dist/dashboard/app/SearchBar.js +31 -0
  169. package/dist/dashboard/app/SidebarTree.js +27 -72
  170. package/dist/dashboard/app/WorkspaceMenu.js +7 -7
  171. package/dist/dashboard/app/WorkspaceRoots.js +12 -12
  172. package/dist/dashboard/app/editor/FileEditor.js +14 -14
  173. package/dist/dashboard/app/modals/CreateEntry.js +3 -3
  174. package/dist/dashboard/app/pages/EntryPage.d.ts +2 -1
  175. package/dist/dashboard/app/pages/EntryPage.js +34 -15
  176. package/dist/dashboard/app/pages/SplashPage.js +37 -37
  177. package/dist/dashboard/app/pages/UsersPage.js +12 -12
  178. package/dist/dashboard/app/ui/DashboardModal.js +15 -14
  179. package/dist/dashboard/atoms/dashboard.js +1 -1
  180. package/dist/dashboard/atoms/entry.js +1 -14
  181. package/dist/dashboard/atoms/entryTable.js +1 -0
  182. package/dist/dashboard/atoms/explorer.d.ts +4 -0
  183. package/dist/dashboard/atoms/explorer.js +19 -7
  184. package/dist/dashboard/atoms/graph.js +1 -1
  185. package/dist/dashboard/atoms/link.js +5 -2
  186. package/dist/dashboard/atoms/move.d.ts +5 -0
  187. package/dist/dashboard/atoms/move.js +29 -3
  188. package/dist/dashboard/atoms/moveConfirmation.d.ts +34 -0
  189. package/dist/dashboard/atoms/moveConfirmation.js +95 -0
  190. package/dist/dashboard/atoms/nav.d.ts +9 -5
  191. package/dist/dashboard/atoms/nav.js +120 -45
  192. package/dist/dashboard/atoms/overview.d.ts +6 -5
  193. package/dist/dashboard/atoms/overview.js +2 -16
  194. package/dist/dashboard/atoms/remove.d.ts +112 -0
  195. package/dist/dashboard/atoms/remove.js +268 -0
  196. package/dist/dashboard/atoms/root.d.ts +2 -2
  197. package/dist/dashboard/atoms/root.js +41 -26
  198. package/dist/dashboard/atoms/user.js +1 -1
  199. package/dist/dashboard/atoms/utils.js +1 -1
  200. package/dist/dashboard/boot/Boot.js +25 -17
  201. package/dist/dashboard/boot/DashboardWorker.d.ts +1 -0
  202. package/dist/dashboard/boot/DashboardWorker.js +10 -46
  203. package/dist/dashboard/boot/LoadWorker.js +17 -10
  204. package/dist/dashboard/cms/EntryTable.js +8 -5
  205. package/dist/database/BrowserEntryStore.d.ts +12 -62
  206. package/dist/database/BrowserEntryStore.js +97 -196
  207. package/dist/database/DatabaseTables.js +2 -2
  208. package/dist/database/EntryDatabase.js +21 -1
  209. package/dist/database/EntryTransaction.js +0 -35
  210. package/dist/database/EntryUrlAliases.d.ts +4 -0
  211. package/dist/database/EntryUrlAliases.js +16 -10
  212. package/dist/database/driver/RuntimeDatabase.js +3 -3
  213. package/dist/database/driver/WasmDatabase.d.ts +5 -18
  214. package/dist/database/driver/WasmDatabase.js +15 -16
  215. package/dist/database/entry/EntryTable.js +2 -2
  216. package/dist/database/query/EntryQuery.js +1 -1
  217. package/dist/database/query/Relation.js +1 -1
  218. package/dist/database/query/Search.js +2 -2
  219. package/dist/field/code/CodeField.view.js +16 -16
  220. package/dist/field/link/EntryLink.d.ts +4 -4
  221. package/dist/field/link/FileLink.d.ts +4 -4
  222. package/dist/field/link/ImageLink.d.ts +4 -4
  223. package/dist/field/link/Link.d.ts +1 -1
  224. package/dist/field/link/LinkField.d.ts +5 -0
  225. package/dist/field/link/LinkField.view.js +244 -370
  226. package/dist/field/link/UrlLink.d.ts +4 -4
  227. package/dist/field/list/ListField.d.ts +2 -0
  228. package/dist/field/list/ListField.view.d.ts +15 -0
  229. package/dist/field/list/ListField.view.js +343 -301
  230. package/dist/field/metadata/MetadataField.d.ts +17 -7
  231. package/dist/field/metadata/MetadataField.js +14 -3
  232. package/dist/field/metadata/MetadataField.view.d.ts +1 -0
  233. package/dist/field/metadata/MetadataField.view.js +25 -21
  234. package/dist/field/richtext/RichTextBlock.d.ts +3 -1
  235. package/dist/field/richtext/RichTextBlock.js +177 -74
  236. package/dist/field/richtext/RichTextBlockHost.js +1 -1
  237. package/dist/field/richtext/RichTextExtensions.js +31 -31
  238. package/dist/field/richtext/RichTextField.view.js +14 -8
  239. package/dist/field/richtext/RichTextInsertMenu.js +43 -27
  240. package/dist/field/views.js +2 -0
  241. package/dist/index.css +490 -309
  242. package/dist/picker/url/UrlPicker.js +1 -0
  243. package/docs/components/badge.md +1 -0
  244. package/docs/components/button.md +1 -0
  245. package/docs/components/content-card.md +1 -0
  246. package/docs/components/search-field.md +1 -0
  247. package/docs/components/sortable-list.md +5 -20
  248. package/docs/components/table.md +1 -0
  249. package/docs/configuration.md +1 -1
  250. package/docs/deploy/self-host.md +1 -1
  251. package/docs/deploy.md +5 -4
  252. package/docs/fields.md +1 -1
  253. package/docs/index.md +1 -1
  254. package/docs/instant-publishing.md +7 -22
  255. package/docs/overviews.md +3 -4
  256. package/docs/upgrading.md +3 -2
  257. package/llms-full.txt +31 -55
  258. package/package.json +2 -3
  259. package/dist/core/db/EntryValidationError.d.ts +0 -13
  260. package/dist/core/db/EntryValidationError.js +0 -23
  261. package/dist/dashboard/app/DeleteDialog.d.ts +0 -11
  262. package/dist/dashboard/atoms/delete.d.ts +0 -68
  263. package/dist/dashboard/atoms/delete.js +0 -143
  264. package/dist/chunks/{chunk-MOWR65LM.js → chunk-PYDOMKJZ.js} +71 -71
@@ -9,10 +9,8 @@ import { getScope } from "alinea/core/Scope";
9
9
  import { trigger } from "alinea/core/Trigger";
10
10
  import { IndexEvent } from "alinea/core/db/IndexEvent";
11
11
  import { EntryUrlConflictError } from "alinea/core/db/EntryUrlConflictError";
12
- import { EntryValidationError } from "alinea/core/db/EntryValidationError";
13
12
  import {
14
13
  BrowserEntryStore,
15
- holdCache,
16
14
  isCorruptDatabaseError
17
15
  } from "alinea/database/BrowserEntryStore";
18
16
  import { EntryStore } from "alinea/database/EntryStore";
@@ -21,7 +19,6 @@ import {
21
19
  } from "./ActivityEvent.js";
22
20
  var remote = pLimit(1);
23
21
  var cacheName = "alinea-entry-database";
24
- var cacheWait = 1e3;
25
22
  var syncInterval = 12e4;
26
23
  var activityHistoryLimit = 100;
27
24
  var DashboardWorker = class extends EventTarget {
@@ -30,14 +27,13 @@ var DashboardWorker = class extends EventTarget {
30
27
  #localClient;
31
28
  #nextLoad = trigger();
32
29
  #defer;
33
- /** Whether this worker holds the cache, see #holdsCache. */
34
- #cacheHeld;
35
30
  #currentRevision;
36
31
  #mutations = [];
37
32
  #activities = [];
38
33
  #local = pLimit(1);
39
34
  #blocked = false;
40
35
  #syncInterval;
36
+ /** Keep the content in memory with `source`, instead of in the browser. */
41
37
  constructor(source) {
42
38
  super();
43
39
  this.#source = source;
@@ -120,7 +116,7 @@ Remote updates: ${remote2}`
120
116
  void this.#flush(item);
121
117
  return item.sha;
122
118
  } catch (error) {
123
- if (error instanceof EntryUrlConflictError || error instanceof EntryValidationError) {
119
+ if (error instanceof EntryUrlConflictError) {
124
120
  this.#cancelMutation(item, error);
125
121
  throw error;
126
122
  }
@@ -284,9 +280,11 @@ Remote updates: ${remote2}`
284
280
  this.#localDB = void 0;
285
281
  this.#localClient = void 0;
286
282
  try {
287
- await this.#defer?.();
288
- this.#defer = void 0;
289
283
  let db = await this.#open(config, revision, false);
284
+ if (this.#defer)
285
+ void this.#defer().catch(() => {
286
+ });
287
+ this.#defer = void 0;
290
288
  let cacheFailure = await this.#syncLocalIndex(db);
291
289
  if (cacheFailure && isCorruptDatabaseError(cacheFailure.error)) {
292
290
  await db.close();
@@ -310,9 +308,7 @@ Remote updates: ${remote2}`
310
308
  });
311
309
  this.#defer = async () => {
312
310
  unsubscribe();
313
- if (db instanceof BrowserEntryStore) await db.release();
314
- void db.close().catch(() => {
315
- });
311
+ await db.close();
316
312
  };
317
313
  } catch (cause) {
318
314
  this.#currentRevision = void 0;
@@ -321,41 +317,9 @@ Remote updates: ${remote2}`
321
317
  throw cause;
322
318
  }
323
319
  }
324
- /**
325
- * Open a store with its content cached in IndexedDB. While another worker
326
- * holds the cache, start from a copy of what it holds without storing,
327
- * and else, or if the cache fails, keep the content in memory only.
328
- */
329
- async #open(config, revision, reset) {
330
- if (globalThis.indexedDB) {
331
- const options = { name: cacheName, revision };
332
- if (await this.#holdsCache()) {
333
- try {
334
- return await BrowserEntryStore.open(config, { ...options, reset });
335
- } catch (error) {
336
- console.warn("Failed to open the content cache", error);
337
- }
338
- } else if (!reset) {
339
- const snapshot = await BrowserEntryStore.snapshot(config, options);
340
- if (snapshot) return snapshot;
341
- }
342
- }
343
- return EntryStore.memory(config, this.#fallbackSource());
344
- }
345
- /**
346
- * Whether this worker holds the cache, which it then keeps for as long as
347
- * it runs: no other worker writes the cache meanwhile, such as one of
348
- * another dashboard build. A worker that does not hold it stores nothing
349
- * until the dashboard loads again.
350
- */
351
- #holdsCache() {
352
- this.#cacheHeld ??= holdCache(cacheName, { wait: cacheWait });
353
- return this.#cacheHeld;
354
- }
355
- #fallbackSource() {
356
- if (!this.#source)
357
- throw new Error("A source is required when IndexedDB is unavailable");
358
- return this.#source;
320
+ #open(config, fingerprint, reset) {
321
+ if (this.#source) return EntryStore.memory(config, this.#source);
322
+ return BrowserEntryStore.open(config, { name: cacheName, fingerprint, reset });
359
323
  }
360
324
  async #syncLocalIndex(db) {
361
325
  const sourceTree = await db.source.getTree();
@@ -5,19 +5,14 @@ import "../../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/dashboard/boot/LoadWorker.ts
7
7
  import { IndexEvent } from "alinea/core/db/IndexEvent";
8
- import { MemorySource } from "alinea/core/source/MemorySource";
9
8
  import { ActivityEvent } from "./ActivityEvent.js";
10
9
  import { DashboardWorker } from "./DashboardWorker.js";
11
10
  async function loadWorker(gen) {
12
- const worker = new DashboardWorker(new MemorySource());
13
- addEventListener("connect", (event) => {
14
- if (!(event instanceof MessageEvent)) return;
15
- console.info("Worker connected");
16
- const port = event.ports[0];
17
- expose(worker, port);
18
- const listen = (event2) => {
11
+ const worker = new DashboardWorker();
12
+ function forward(target) {
13
+ const listen = (event) => {
19
14
  try {
20
- port.postMessage({ ...event2, type: event2.type });
15
+ target.postMessage({ ...event, type: event.type });
21
16
  } catch {
22
17
  worker.removeEventListener(IndexEvent.type, listen);
23
18
  worker.removeEventListener(ActivityEvent.type, listen);
@@ -25,7 +20,19 @@ async function loadWorker(gen) {
25
20
  };
26
21
  worker.addEventListener(IndexEvent.type, listen);
27
22
  worker.addEventListener(ActivityEvent.type, listen);
28
- });
23
+ }
24
+ if ("onconnect" in globalThis) {
25
+ addEventListener("connect", (event) => {
26
+ if (!(event instanceof MessageEvent)) return;
27
+ console.info("Worker connected");
28
+ const port = event.ports[0];
29
+ expose(worker, port);
30
+ forward(port);
31
+ });
32
+ } else {
33
+ expose(worker, globalThis);
34
+ forward(globalThis);
35
+ }
29
36
  for await (const batch of gen) {
30
37
  await worker.load(batch.configFingerprint, batch.config, batch.client);
31
38
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  unwrap
3
- } from "../../chunks/chunk-TIJTT5ZI.js";
3
+ } from "../../chunks/chunk-G43W7AME.js";
4
4
  import {
5
5
  useAtomValueRaw,
6
6
  useAtomValueRawSync,
@@ -37,6 +37,7 @@ import {
37
37
  import { Schema } from "alinea/core/Schema";
38
38
  import { getScope } from "alinea/core/Scope";
39
39
  import { Type } from "alinea/core/Type";
40
+ import { EntryStatusIcon, entryStatus } from "../app/EntryStatusIcon.js";
40
41
  import {
41
42
  OverviewCell,
42
43
  overviewCellText,
@@ -65,9 +66,9 @@ import {
65
66
  var EntryTable_default = {
66
67
  "EntryTable-loading": "alinea-EntryTable-loading",
67
68
  "EntryTableLoading": "alinea-EntryTable-loading",
69
+ "EntryTable": "alinea-EntryTable",
68
70
  "EntryTable-table": "alinea-EntryTable-table",
69
- "EntryTableTable": "alinea-EntryTable-table",
70
- "EntryTable": "alinea-EntryTable"
71
+ "EntryTableTable": "alinea-EntryTable-table"
71
72
  };
72
73
 
73
74
  // src/dashboard/cms/EntryTable.tsx
@@ -234,17 +235,19 @@ function EntryTableRow({ config, overview, row }) {
234
235
  const texts = overview.columns.map(
235
236
  (column) => overviewCellText(config, column, row, links)
236
237
  );
238
+ const status = entryStatus(row);
237
239
  return /* @__PURE__ */ jsxs(
238
240
  TableRow,
239
241
  {
240
242
  id: row.id,
241
- textValue: [row.title, ...texts].filter(Boolean).join(" "),
243
+ textValue: [row.title, status?.label, ...texts].filter(Boolean).join(" "),
242
244
  children: [
243
245
  /* @__PURE__ */ jsx(
244
246
  TableTitle,
245
247
  {
246
248
  icon: type && getType(type).icon || LucideFile,
247
- title: row.title
249
+ title: row.title,
250
+ suffix: status && /* @__PURE__ */ jsx(EntryStatusIcon, { status })
248
251
  }
249
252
  ),
250
253
  overview.columns.map((column, index) => /* @__PURE__ */ jsx(
@@ -1,84 +1,34 @@
1
1
  import type { Config } from 'alinea/core/Config';
2
- import type { Mutation } from 'alinea/core/db/Mutation';
3
- import type { CommitRequest } from 'alinea/core/db/CommitRequest';
4
2
  import type { SyncOptions } from 'alinea/core/db/LocalStore';
5
3
  import type { RemoteSource } from 'alinea/core/source/Source';
4
+ import { type SnapshotStorage } from '@alinea/sqlite-wasm/snapshots';
6
5
  import { EntryStore } from './EntryStore.js';
7
6
  export interface BrowserEntryStoreOptions {
8
7
  name: string;
9
- revision: string;
8
+ /** Stores of one config fingerprint open the bases it wrote. */
9
+ fingerprint: string;
10
+ /** Where bases are kept, Blobs in IndexedDB by default. */
11
+ storage?: SnapshotStorage;
10
12
  /**
11
- * Start over from an empty database instead of the stored one, such as
13
+ * Start over from an empty database instead of a stored base, such as
12
14
  * when it turned out to be corrupt.
13
15
  */
14
16
  reset?: boolean;
15
- /** The IndexedDB implementation, the global one by default. */
16
- indexedDB?: IDBFactory;
17
- IDBKeyRange?: typeof IDBKeyRange;
18
- /** The Web Locks implementation, the global one by default. */
19
- locks?: CacheLocks;
20
- }
21
- /** The part of the Web Locks API that keeps a cache to one worker. */
22
- export interface CacheLocks {
23
- request(name: string, options: {
24
- ifAvailable?: boolean;
25
- signal?: AbortSignal;
26
- }, callback: (lock: Lock | null) => Promise<void> | void): Promise<unknown>;
27
- }
28
- export interface HoldCacheOptions {
29
- /**
30
- * How long to wait, in milliseconds, for another worker that holds the
31
- * cache to end: none by default, Infinity until it does.
32
- */
33
- wait?: number;
34
- /** The Web Locks implementation, the global one by default. */
35
- locks?: CacheLocks;
36
17
  }
37
18
  /**
38
- * Hold the cache called `name` for as long as this worker runs, and resolve
39
- * with whether it does. Nothing coordinates two stores writing the same
40
- * pages, which corrupts them, so only the worker that holds the cache opens
41
- * it; another one, such as one of another dashboard build, reads a
42
- * snapshot. Without Web Locks, or if they refuse, no worker holds it.
43
- */
44
- export declare function holdCache(name: string, options?: HoldCacheOptions): Promise<boolean>;
45
- /**
46
- * WASM entry store kept in IndexedDB: every commit stores the pages it
47
- * changed.
19
+ * WASM entry store on a copy-on-write overlay over the newest stored base:
20
+ * it reads pages from the base as queries need them, and keeps its changes
21
+ * in memory. Every sync stores them as a new base in the background, which
22
+ * the next store opens; the stored bases never change, so stores of any
23
+ * number of workers and dashboard builds can use them at once.
48
24
  */
49
25
  export declare class BrowserEntryStore extends EntryStore {
50
26
  #private;
51
- /**
52
- * Open the store kept in IndexedDB. Only the worker that holds the cache
53
- * opens it (see `holdCache`), and deletes the caches of other versions.
54
- */
55
27
  static open(config: Config, options: BrowserEntryStoreOptions): Promise<BrowserEntryStore>;
56
- /**
57
- * A copy in memory of the stored content, for a worker that cannot store
58
- * it while another one does: it loads the last committed state and never
59
- * writes. Resolves with undefined if that state cannot be read.
60
- */
61
- static snapshot(config: Config, options: BrowserEntryStoreOptions): Promise<EntryStore | undefined>;
62
28
  private constructor();
63
- /**
64
- * Opening, and syncing, resolve once the content is in memory: storing it
65
- * is a cache that finishes in the background, which takes seconds for a
66
- * first sync or a rebuild of a large project.
67
- */
68
29
  sync(): Promise<string>;
69
30
  syncWith(remote: RemoteSource, options?: SyncOptions): Promise<string>;
70
- mutate(mutations: Array<Mutation>): Promise<{
71
- sha: string;
72
- }>;
73
- write(request: CommitRequest): Promise<{
74
- sha: string;
75
- }>;
76
- /**
77
- * Stop storing, and resolve once the commits so far are stored, for a
78
- * replacement to open the cache. Work in flight then changes only this
79
- * store's memory, so the replacement need not wait for it.
80
- */
81
- release(): Promise<void>;
31
+ /** Close once the last checkpoint is stored. */
82
32
  close(): Promise<void>;
83
33
  }
84
34
  /** Whether an error, or one it holds, reports a damaged SQLite database. */
@@ -5,196 +5,105 @@ import "../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/database/BrowserEntryStore.ts
7
7
  import { ReadonlyTree } from "alinea/core/source/Tree";
8
- import { TaskQueue } from "alinea/core/util/Async";
9
8
  import { isRecord } from "alinea/core/util/Objects";
10
9
  import {
11
- indexedDBStorage
12
- } from "@alinea/sqlite-wasm/indexeddb";
13
- import { versionedCacheName } from "./Version.js";
10
+ indexedDBSnapshots
11
+ } from "@alinea/sqlite-wasm/snapshots";
12
+ import { databaseVersion, versionedCacheName } from "./Version.js";
14
13
  import { EntryDatabase } from "./EntryDatabase.js";
15
14
  import { EntryStore } from "./EntryStore.js";
16
15
  import { DatabaseSource } from "./DatabaseSource.js";
17
- import {
18
- syncWasmDatabase
19
- } from "./driver/WasmDatabase.js";
20
- var buildTable = sql.identifier("alinea_dashboard_build");
21
- function holdCache(name, options = {}) {
22
- const locks = options.locks ?? globalThis.navigator?.locks;
23
- if (!locks) return Promise.resolve(false);
24
- const { wait = 0 } = options;
25
- const timeout = wait > 0 && wait < Infinity ? new AbortController() : void 0;
26
- const timer = timeout && setTimeout(() => timeout.abort(), wait);
27
- const request = wait === 0 ? { ifAvailable: true } : timeout ? { signal: timeout.signal } : {};
28
- return new Promise((resolve) => {
29
- try {
30
- locks.request(storageNameOf(name), request, (lock) => {
31
- clearTimeout(timer);
32
- resolve(Boolean(lock));
33
- if (lock) return new Promise(() => {
34
- });
35
- }).catch(() => resolve(false));
36
- } catch {
37
- resolve(false);
38
- }
39
- });
40
- }
16
+ import { wasmHandle, wasmSqlite } from "./driver/WasmDatabase.js";
17
+ var cacheSize = -65536;
18
+ var keepBranches = 2;
41
19
  var BrowserEntryStore = class _BrowserEntryStore extends EntryStore {
42
- #handle;
43
- #persistQueue = new TaskQueue();
44
- #closed = false;
45
- #detached = false;
46
- /**
47
- * Open the store kept in IndexedDB. Only the worker that holds the cache
48
- * opens it (see `holdCache`), and deletes the caches of other versions.
49
- */
20
+ #session;
21
+ #storage;
22
+ #stored;
23
+ #written = Promise.resolve();
24
+ #cleanup = Promise.resolve();
50
25
  static async open(config, options) {
51
- const storageName = storageNameOf(options.name);
52
- const factory = options.indexedDB ?? indexedDB;
53
- const storage = indexedDBStorage(storageName, {
54
- indexedDB: factory,
55
- IDBKeyRange: options.IDBKeyRange ?? IDBKeyRange
56
- });
57
- const openStorage = () => _BrowserEntryStore.#openStorage(config, options, storage);
58
- if (options.reset) await storage.delete();
59
- let store;
60
- try {
61
- store = await openStorage();
62
- } catch {
63
- await storage.delete();
64
- store = await openStorage();
65
- }
66
- await cleanupOldCaches({
67
- factory,
68
- locks: options.locks ?? globalThis.navigator?.locks,
69
- baseName: options.name,
70
- currentName: storageName
26
+ const { name, fingerprint } = options;
27
+ const storage = options.storage ?? indexedDBSnapshots(`${versionedCacheName(name)}-snapshots`);
28
+ const cleanup = !options.storage && globalThis.indexedDB ? cleanupOldCaches(indexedDB, name).catch(() => {
29
+ }) : Promise.resolve();
30
+ const Database = await wasmSqlite();
31
+ const session = options.reset ? storage.session(new Database(), { branch: fingerprint }) : await openBase(storage, Database, fingerprint);
32
+ const store = await _BrowserEntryStore.#on(
33
+ config,
34
+ session,
35
+ storage,
36
+ fingerprint
37
+ ).catch((error) => {
38
+ if (!session.snapshot) throw error;
39
+ return _BrowserEntryStore.#on(
40
+ config,
41
+ storage.session(new Database(), { branch: fingerprint }),
42
+ storage,
43
+ fingerprint
44
+ );
71
45
  });
46
+ store.#cleanup = cleanup;
72
47
  return store;
73
48
  }
74
- /**
75
- * A copy in memory of the stored content, for a worker that cannot store
76
- * it while another one does: it loads the last committed state and never
77
- * writes. Resolves with undefined if that state cannot be read.
78
- */
79
- static async snapshot(config, options) {
80
- const storage = indexedDBStorage(storageNameOf(options.name), {
81
- indexedDB: options.indexedDB ?? indexedDB,
82
- IDBKeyRange: options.IDBKeyRange ?? IDBKeyRange
83
- });
84
- let handle;
85
- try {
86
- handle = await syncWasmDatabase(storage);
87
- handle.detach();
88
- const db = handle.database;
89
- const stored = await storedBuild(db);
90
- if (stored === void 0) {
91
- await db.close();
92
- return void 0;
93
- }
94
- await EntryDatabase.createSchema(db, config, ReadonlyTree.EMPTY.sha);
95
- const database = new EntryDatabase(config, db, { fork: handle.fork });
96
- if (stored !== options.revision) await database.reindex(config);
97
- return new EntryStore(config, database, new DatabaseSource(database), {
98
- ownsDatabase: true,
99
- sourceFollowsDatabase: true
100
- });
101
- } catch (error) {
102
- if (handle) await handle.database.close();
103
- console.warn("Failed to read the content cache", error);
104
- return void 0;
49
+ static async #on(config, session, storage, fingerprint) {
50
+ const sqlite = session.db;
51
+ const branch = session.snapshot?.branch;
52
+ sqlite.run(`pragma cache_size = ${cacheSize}`);
53
+ const { database: db, fork } = wasmHandle(sqlite);
54
+ async function preview() {
55
+ const copy = await fork();
56
+ await copy.database.run(sql`pragma cache_size = -8192`);
57
+ return copy;
105
58
  }
106
- }
107
- static async #openStorage(config, options, storage) {
108
- const handle = await syncWasmDatabase(storage);
109
59
  try {
110
- const db = handle.database;
111
- await db.run(sql`pragma page_size = 65536`);
112
- const stored = await storedBuild(db);
60
+ await db.run(sql`pragma page_size = 16384`);
113
61
  await EntryDatabase.createSchema(db, config, ReadonlyTree.EMPTY.sha);
114
- const database = new EntryDatabase(config, db, { fork: handle.fork });
115
- if (stored !== void 0 && stored !== options.revision)
116
- await database.reindex(config);
117
- if (stored !== options.revision)
118
- await db.run(
119
- sql`insert or replace into ${buildTable} (id, revision)
120
- values (1, ${options.revision})`
121
- );
122
- return new _BrowserEntryStore(config, database, handle);
62
+ const database = new EntryDatabase(config, db, { fork: preview });
63
+ if (branch !== void 0 && branch !== fingerprint)
64
+ await withoutJournal(sqlite, () => database.reindex(config));
65
+ return new _BrowserEntryStore(config, database, session, storage);
123
66
  } catch (error) {
124
- await handle.database.close();
67
+ await db.close();
125
68
  throw error;
126
69
  }
127
70
  }
128
- #detach() {
129
- if (this.#detached) return;
130
- this.#detached = true;
131
- this.#handle.detach();
132
- }
133
- constructor(config, database, handle) {
71
+ constructor(config, database, session, storage) {
134
72
  super(config, database, new DatabaseSource(database), {
135
73
  ownsDatabase: true,
136
74
  sourceFollowsDatabase: true
137
75
  });
138
- this.#handle = handle;
76
+ this.#session = session;
77
+ this.#storage = storage;
139
78
  }
140
- /**
141
- * Opening, and syncing, resolve once the content is in memory: storing it
142
- * is a cache that finishes in the background, which takes seconds for a
143
- * first sync or a rebuild of a large project.
144
- */
145
- sync() {
146
- return this.#run(() => super.sync());
79
+ async sync() {
80
+ return this.#checkpoint(await super.sync());
147
81
  }
148
- syncWith(remote, options) {
149
- return this.#run(() => super.syncWith(remote, options));
82
+ async syncWith(remote, options) {
83
+ return this.#checkpoint(await super.syncWith(remote, options));
150
84
  }
151
- mutate(mutations) {
152
- return this.#persistAfter(() => super.mutate(mutations));
153
- }
154
- write(request) {
155
- return this.#persistAfter(() => super.write(request));
156
- }
157
- /** Resolve once the commits of a task that changed this store are stored. */
158
- #persistAfter(task) {
159
- return this.#run(async () => {
160
- const result = await task();
161
- await this.#store();
162
- return result;
163
- });
164
- }
165
- /**
166
- * Wait for the commits so far to be stored. The cache only saves loading
167
- * time: if storing fails, such as over quota, it stops instead of failing
168
- * the commit, and keeps the last committed state it stored.
169
- */
170
- async #store() {
171
- if (this.#detached) return;
172
- try {
173
- await this.#handle.flush();
174
- } catch (error) {
175
- console.warn("Stopped caching content in IndexedDB", error);
176
- this.#detach();
177
- }
178
- }
179
- /**
180
- * Stop storing, and resolve once the commits so far are stored, for a
181
- * replacement to open the cache. Work in flight then changes only this
182
- * store's memory, so the replacement need not wait for it.
183
- */
184
- async release() {
185
- this.#detach();
186
- await this.#handle.flush().catch(() => {
85
+ /** Store the content at `revision` as a base, in the background. */
86
+ #checkpoint(revision) {
87
+ if (!this.#storage.supported() || this.#stored === revision) return revision;
88
+ this.#stored = revision;
89
+ const session = this.#session;
90
+ const storage = this.#storage;
91
+ const key = `${revision}-${session.branch}`;
92
+ const save = (key2) => this.database.whileIdle(async () => ({
93
+ saved: session.held ? session.save({ key: key2, meta: { tree: revision } }) : void 0
94
+ })).then(({ saved }) => saved);
95
+ const written = save(key).then(
96
+ (result) => result?.status === "mismatch" ? save(`${key}-${crypto.randomUUID()}`) : result
97
+ ).then(() => storage.retain({ branches: keepBranches })).catch(() => {
98
+ if (this.#stored === revision) this.#stored = void 0;
187
99
  });
100
+ this.#written = Promise.all([this.#written, written]);
101
+ return revision;
188
102
  }
189
- #run(task) {
190
- if (this.#closed)
191
- return Promise.reject(new Error("BrowserEntryStore is closed"));
192
- return this.#persistQueue.run(task);
193
- }
194
- close() {
195
- if (this.#closed) return this.#persistQueue.drain();
196
- this.#closed = true;
197
- return this.#persistQueue.run(() => super.close());
103
+ /** Close once the last checkpoint is stored. */
104
+ async close() {
105
+ await Promise.all([this.#cleanup, this.#written]);
106
+ await super.close();
198
107
  }
199
108
  };
200
109
  var corruptCodes = /* @__PURE__ */ new Set(["SQLITE_CORRUPT", "SQLITE_NOTADB"]);
@@ -208,43 +117,36 @@ function isCorruptDatabaseError(error) {
208
117
  const errors = Array.isArray(error.errors) ? error.errors : [];
209
118
  return isCorruptDatabaseError(error.cause) || errors.some(isCorruptDatabaseError);
210
119
  }
211
- function storageNameOf(name) {
212
- return `${versionedCacheName(name)}-pages`;
120
+ async function withoutJournal(sqlite, task) {
121
+ const [[mode]] = sqlite.exec("pragma journal_mode")[0].values;
122
+ sqlite.run("pragma journal_mode = off");
123
+ try {
124
+ return await task();
125
+ } finally {
126
+ sqlite.run(`pragma journal_mode = ${mode}`);
127
+ }
213
128
  }
214
- async function storedBuild(db) {
215
- await db.run(sql`create table if not exists ${buildTable} (
216
- id integer primary key,
217
- revision text not null
218
- )`);
219
- const row = await db.get(
220
- sql`select revision from ${buildTable} where id = 1`
221
- );
222
- if (row) return row.revision;
223
- const entries = await db.get(
224
- sql`select count(*) as count from sqlite_master
225
- where type = 'table' and name = 'alinea_entry_index'`
226
- );
227
- return entries?.count ? "" : void 0;
129
+ async function openBase(storage, Database, fingerprint) {
130
+ const empty = () => storage.session(new Database(), { branch: fingerprint });
131
+ if (!storage.supported()) return empty();
132
+ return storage.open(Database, {
133
+ branch: fingerprint,
134
+ choose: (bases) => bases.find((base) => base.branch === fingerprint) ?? bases[0]
135
+ }).catch(empty);
228
136
  }
229
- async function cleanupOldCaches({
230
- factory,
231
- locks,
232
- baseName,
233
- currentName
234
- }) {
137
+ async function cleanupOldCaches(factory, name) {
235
138
  if (!factory.databases) return;
236
139
  const databases = await factory.databases().catch(() => []);
237
- const names = databases.flatMap(
238
- (database) => database.name && database.name !== currentName && (database.name === baseName || database.name.startsWith(`${baseName}-`)) ? [database.name] : []
140
+ const stale = databases.flatMap(
141
+ (database) => database.name && isStale(database.name, name) ? [database.name] : []
239
142
  );
240
- await Promise.all(names.map((name) => deleteUnheldCache(factory, locks, name)));
143
+ await Promise.all(stale.map((cache) => deleteCache(factory, cache)));
241
144
  }
242
- async function deleteUnheldCache(factory, locks, name) {
243
- if (!locks) return deleteCache(factory, name);
244
- await locks.request(name, { ifAvailable: true }, (lock) => {
245
- if (lock) return deleteCache(factory, name);
246
- }).catch(() => {
247
- });
145
+ function isStale(cache, name) {
146
+ if (cache === name || cache === `${versionedCacheName(name)}-pages`)
147
+ return true;
148
+ if (!cache.startsWith(`${name}-v`)) return false;
149
+ return Number.parseInt(cache.slice(name.length + 2)) < databaseVersion;
248
150
  }
249
151
  function deleteCache(factory, name) {
250
152
  return new Promise((resolve) => {
@@ -256,6 +158,5 @@ function deleteCache(factory, name) {
256
158
  }
257
159
  export {
258
160
  BrowserEntryStore,
259
- holdCache,
260
161
  isCorruptDatabaseError
261
162
  };
@@ -4,10 +4,10 @@ import {
4
4
  number,
5
5
  text,
6
6
  varchar
7
- } from "../chunks/chunk-ASYKC6IE.js";
7
+ } from "../chunks/chunk-5H4D5JQL.js";
8
8
  import {
9
9
  table
10
- } from "../chunks/chunk-MOWR65LM.js";
10
+ } from "../chunks/chunk-PYDOMKJZ.js";
11
11
  import "../chunks/chunk-76VMZTLN.js";
12
12
  import "../chunks/chunk-2PIFHEYR.js";
13
13
  import "../chunks/chunk-K62JP6HR.js";