alinea 2.0.0-preview.7 → 2.0.0-preview.8

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 (283) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/LICENSES.md +120 -120
  3. package/dist/adapter/next/cms.d.ts +18 -4
  4. package/dist/adapter/next/cms.js +125 -36
  5. package/dist/adapter/next/handler.d.ts +5 -1
  6. package/dist/adapter/next/handler.js +14 -7
  7. package/dist/adapter/next/node.d.ts +5 -0
  8. package/dist/adapter/next/node.js +16 -0
  9. package/dist/adapter/next/previews.d.ts +4 -1
  10. package/dist/adapter/next/previews.js +13 -1
  11. package/dist/adapter/next/renderStats.d.ts +24 -0
  12. package/dist/adapter/next/renderStats.js +74 -0
  13. package/dist/adapter/next/syncCheck.d.ts +8 -2
  14. package/dist/adapter/next/syncCheck.js +3 -3
  15. package/dist/adapter/next/with-alinea.js +0 -18
  16. package/dist/backend/Handler.d.ts +2 -2
  17. package/dist/backend/Handler.js +33 -12
  18. package/dist/backend/api/CreateBackend.js +15 -3
  19. package/dist/backend/api/DatabaseApi.js +22 -135
  20. package/dist/backend/api/database.js +15 -3
  21. package/dist/backend/resolver/ParsePreview.d.ts +2 -2
  22. package/dist/backend/router/NodeHandler.js +2 -0
  23. package/dist/backend/store/GeneratedDatabase.d.ts +5 -0
  24. package/dist/backend/store/GeneratedDatabase.js +31 -0
  25. package/dist/backend/store/GeneratedDatabaseNode.d.ts +4 -0
  26. package/dist/backend/store/GeneratedDatabaseNode.js +35 -0
  27. package/dist/backend/util/ORM.js +4 -2
  28. package/dist/bundled.d.ts +1470 -442
  29. package/dist/chunks/bun-sqlite-ZZDB7YQQ.js +18 -0
  30. package/dist/chunks/chunk-2BJ2IFQM.js +1085 -0
  31. package/dist/chunks/chunk-2PIFHEYR.js +364 -0
  32. package/dist/chunks/{chunk-X47OVDF3.js → chunk-2U43BB6P.js} +356 -1000
  33. package/dist/chunks/chunk-76VMZTLN.js +55 -0
  34. package/dist/chunks/chunk-A42DP5T7.js +94 -0
  35. package/dist/chunks/chunk-ATF42T2B.js +35 -0
  36. package/dist/chunks/{chunk-NWQXN4SB.js → chunk-ETBQTP2S.js} +40 -29
  37. package/dist/chunks/chunk-IL7FJQAY.js +94 -0
  38. package/dist/chunks/chunk-K62JP6HR.js +22 -0
  39. package/dist/chunks/chunk-K7LHQXI4.js +103 -0
  40. package/dist/chunks/chunk-M7KDJEGH.js +495 -0
  41. package/dist/chunks/chunk-N47Y62CI.js +396 -0
  42. package/dist/chunks/chunk-NLZLRQ5U.js +100 -0
  43. package/dist/chunks/chunk-QBPYWU6C.js +24 -0
  44. package/dist/chunks/{chunk-6P4UVRPH.js → chunk-RWWYUNVX.js} +2 -1
  45. package/dist/chunks/chunk-TFMUSWGG.js +605 -0
  46. package/dist/chunks/{chunk-I4GC3E65.js → chunk-UPY2RUZF.js} +0 -1
  47. package/dist/chunks/chunk-XCZXR3C2.js +112 -0
  48. package/dist/chunks/chunk-XTWYBWP2.js +81 -0
  49. package/dist/chunks/node-sqlite-7CSGL27V.js +18 -0
  50. package/dist/cli/Generate.js +74 -64
  51. package/dist/cli/Serve.js +1 -1
  52. package/dist/cli/bin.js +2 -1
  53. package/dist/cli/generate/CopyStaticFiles.js +17 -14
  54. package/dist/cli/generate/DevDB.d.ts +22 -5
  55. package/dist/cli/generate/DevDB.js +136 -24
  56. package/dist/cli/serve/CreateLocalServer.js +22 -4
  57. package/dist/cli/serve/LiveReload.d.ts +1 -1
  58. package/dist/cli/serve/LiveReload.js +3 -0
  59. package/dist/cloud/CloudRemote.js +2 -1
  60. package/dist/components/Calendar.js +6 -6
  61. package/dist/components/Checkbox.js +3 -3
  62. package/dist/components/ColorSwatchPicker.js +2 -2
  63. package/dist/components/ComboBox.js +9 -9
  64. package/dist/components/DateField.js +1 -1
  65. package/dist/components/DatePicker.js +6 -6
  66. package/dist/components/DateRangePicker.js +6 -6
  67. package/dist/components/Disclosure.js +9 -9
  68. package/dist/components/Label.js +5 -5
  69. package/dist/components/List.js +33 -33
  70. package/dist/components/Menu.js +3 -3
  71. package/dist/components/Modal.js +3 -3
  72. package/dist/components/MultipleSelect.js +25 -25
  73. package/dist/components/NumberField.js +6 -6
  74. package/dist/components/ProgressCircle.js +3 -3
  75. package/dist/components/RadioGroup.js +2 -2
  76. package/dist/components/SearchField.js +5 -5
  77. package/dist/components/Select.js +7 -7
  78. package/dist/components/Surface.js +2 -2
  79. package/dist/components/Table.js +6 -6
  80. package/dist/components/Tabs.js +2 -2
  81. package/dist/components/TextField.js +6 -6
  82. package/dist/components/Toggle.js +2 -2
  83. package/dist/components/Toolbar.js +2 -2
  84. package/dist/components/Tooltip.js +4 -4
  85. package/dist/components/Tree.d.ts +2 -1
  86. package/dist/components/Tree.js +5 -5
  87. package/dist/core/Document.js +15 -3
  88. package/dist/core/Entry.d.ts +3 -0
  89. package/dist/core/Entry.js +4 -0
  90. package/dist/core/EntrySeed.d.ts +19 -0
  91. package/dist/core/EntrySeed.js +136 -0
  92. package/dist/core/Field.d.ts +7 -0
  93. package/dist/core/Field.js +4 -0
  94. package/dist/core/Filter.d.ts +40 -2
  95. package/dist/core/Filter.js +145 -0
  96. package/dist/core/Graph.d.ts +9 -0
  97. package/dist/core/ListRow.d.ts +1 -0
  98. package/dist/core/ListRow.js +1 -0
  99. package/dist/core/Reference.d.ts +1 -0
  100. package/dist/core/Root.js +1 -1
  101. package/dist/core/Scope.d.ts +2 -0
  102. package/dist/core/Scope.js +16 -1
  103. package/dist/core/Type.d.ts +2 -1
  104. package/dist/core/Type.js +13 -1
  105. package/dist/core/db/EntryAliases.js +1 -11
  106. package/dist/core/db/EntryReference.d.ts +0 -6
  107. package/dist/core/db/IndexEvent.d.ts +2 -1
  108. package/dist/core/db/IndexEvent.js +11 -3
  109. package/dist/core/db/LinkResolver.d.ts +6 -6
  110. package/dist/core/db/LinkResolver.js +0 -52
  111. package/dist/core/db/LocalStore.d.ts +25 -0
  112. package/dist/core/field/ListField.js +19 -0
  113. package/dist/core/field/RecordField.js +8 -0
  114. package/dist/core/field/RichTextField.js +42 -0
  115. package/dist/core/field/UnionField.js +8 -0
  116. package/dist/core/source/FSSource.d.ts +14 -2
  117. package/dist/core/source/FSSource.js +58 -13
  118. package/dist/core/source/GitUtils.d.ts +4 -0
  119. package/dist/core/source/GitUtils.js +9 -10
  120. package/dist/core/source/GithubSource.js +22 -11
  121. package/dist/core/source/IndexedDBSource.d.ts +2 -1
  122. package/dist/core/source/IndexedDBSource.js +67 -53
  123. package/dist/core/source/MemorySource.js +5 -3
  124. package/dist/core/source/OverlaySource.d.ts +5 -2
  125. package/dist/core/source/OverlaySource.js +31 -3
  126. package/dist/core/source/Source.d.ts +3 -0
  127. package/dist/core/source/Source.js +15 -2
  128. package/dist/core/source/Tree.d.ts +10 -7
  129. package/dist/core/source/Tree.js +118 -70
  130. package/dist/core/source/Utils.js +6 -3
  131. package/dist/core/util/Arrays.d.ts +1 -0
  132. package/dist/core/util/Arrays.js +10 -0
  133. package/dist/core/util/Async.d.ts +7 -0
  134. package/dist/core/util/Async.js +15 -0
  135. package/dist/core/util/Checks.js +1 -1
  136. package/dist/core/util/EntryFilenames.d.ts +22 -1
  137. package/dist/core/util/EntryFilenames.js +56 -2
  138. package/dist/core/util/IndexedDB.d.ts +4 -0
  139. package/dist/core/util/IndexedDB.js +20 -0
  140. package/dist/core/util/Paths.js +5 -1
  141. package/dist/dashboard/App.js +3 -3
  142. package/dist/dashboard/ViewKeys.d.ts +1 -0
  143. package/dist/dashboard/ViewKeys.js +1 -0
  144. package/dist/dashboard/app/AccessDenied.js +4 -4
  145. package/dist/dashboard/app/ActivityStatus.js +11 -11
  146. package/dist/dashboard/app/AppShell.js +2 -2
  147. package/dist/dashboard/app/AuthView.js +9 -9
  148. package/dist/dashboard/app/Badge.js +2 -2
  149. package/dist/dashboard/app/CompactField.js +10 -10
  150. package/dist/dashboard/app/DashboardErrorBoundary.js +6 -6
  151. package/dist/dashboard/app/EntryFields.js +2 -2
  152. package/dist/dashboard/app/EntryHeader.js +10 -10
  153. package/dist/dashboard/app/EntryReferences.js +8 -8
  154. package/dist/dashboard/app/EntrySidebar.js +8 -8
  155. package/dist/dashboard/app/EntrySidebarPreview.js +11 -11
  156. package/dist/dashboard/app/EntryTranslationBanner.js +6 -6
  157. package/dist/dashboard/app/Explorer.js +44 -44
  158. package/dist/dashboard/app/ExplorerCards.js +30 -30
  159. package/dist/dashboard/app/ExplorerFileCard.js +17 -17
  160. package/dist/dashboard/app/ExplorerList.js +14 -14
  161. package/dist/dashboard/app/ExplorerModal.js +5 -5
  162. package/dist/dashboard/app/ExplorerTable.js +47 -47
  163. package/dist/dashboard/app/LinkPicker.js +2 -2
  164. package/dist/dashboard/app/LocaleMenu.js +6 -6
  165. package/dist/dashboard/app/SidebarTree.js +76 -40
  166. package/dist/dashboard/app/WorkspaceMenu.js +10 -10
  167. package/dist/dashboard/app/WorkspaceRoots.js +27 -27
  168. package/dist/dashboard/app/editor/FileEditor.js +24 -24
  169. package/dist/dashboard/app/editor/FilePreview.js +6 -6
  170. package/dist/dashboard/app/modals/CreateEntry.js +4 -4
  171. package/dist/dashboard/app/pages/EntryPage.js +15 -15
  172. package/dist/dashboard/app/pages/RootPage.js +3 -3
  173. package/dist/dashboard/app/pages/SplashPage.js +47 -47
  174. package/dist/dashboard/app/pages/UsersPage.js +36 -36
  175. package/dist/dashboard/app/ui/DashboardModal.js +17 -17
  176. package/dist/dashboard/app/ui/Rail.js +4 -4
  177. package/dist/dashboard/atoms/config.d.ts +4 -4
  178. package/dist/dashboard/atoms/entry.d.ts +8 -9
  179. package/dist/dashboard/atoms/entry.js +34 -4
  180. package/dist/dashboard/atoms/explorer.d.ts +2 -0
  181. package/dist/dashboard/atoms/explorer.js +7 -2
  182. package/dist/dashboard/boot/Boot.js +2 -3
  183. package/dist/dashboard/boot/DashboardWorker.d.ts +3 -3
  184. package/dist/dashboard/boot/DashboardWorker.js +78 -27
  185. package/dist/dashboard/boot/LoadWorker.js +2 -3
  186. package/dist/dashboard/icons.d.ts +2 -0
  187. package/dist/dashboard/icons.js +40 -0
  188. package/dist/dashboard/plugins/alineaFixturePlugin.js +14 -3
  189. package/dist/database/BrowserEntryStore.d.ts +30 -0
  190. package/dist/database/BrowserEntryStore.js +168 -0
  191. package/dist/database/DatabaseSource.d.ts +13 -0
  192. package/dist/database/DatabaseSource.js +25 -0
  193. package/dist/database/DatabaseTables.d.ts +38 -0
  194. package/dist/database/DatabaseTables.js +53 -0
  195. package/dist/database/EntryDatabase.d.ts +21 -0
  196. package/dist/database/EntryDatabase.js +138 -0
  197. package/dist/database/EntryLayer.d.ts +93 -0
  198. package/dist/database/EntryLayer.js +459 -0
  199. package/dist/database/EntryStore.d.ts +60 -0
  200. package/dist/database/EntryStore.js +269 -0
  201. package/dist/database/EntryTransaction.d.ts +33 -0
  202. package/dist/database/EntryTransaction.js +889 -0
  203. package/dist/database/EntryUrlAliases.d.ts +2 -0
  204. package/dist/database/EntryUrlAliases.js +69 -0
  205. package/dist/database/LocalDB.d.ts +39 -0
  206. package/dist/database/LocalDB.js +79 -0
  207. package/dist/database/Version.d.ts +9 -0
  208. package/dist/database/Version.js +13 -0
  209. package/dist/database/driver/RuntimeDatabase.d.ts +8 -0
  210. package/dist/database/driver/RuntimeDatabase.js +33 -0
  211. package/dist/database/driver/WasmDatabase.d.ts +8 -0
  212. package/dist/database/driver/WasmDatabase.js +33 -0
  213. package/dist/database/entry/EntryData.d.ts +13 -0
  214. package/dist/database/entry/EntryData.js +51 -0
  215. package/dist/database/entry/EntryTable.d.ts +173 -0
  216. package/dist/database/entry/EntryTable.js +137 -0
  217. package/dist/database/entry/EntryView.d.ts +22 -0
  218. package/dist/database/entry/EntryView.js +106 -0
  219. package/dist/database/query/Condition.d.ts +8 -0
  220. package/dist/database/query/Condition.js +117 -0
  221. package/dist/database/query/EntryQuery.d.ts +43 -0
  222. package/dist/database/query/EntryQuery.js +414 -0
  223. package/dist/database/query/EntryReferences.d.ts +6 -0
  224. package/dist/database/query/EntryReferences.js +68 -0
  225. package/dist/database/query/Relation.d.ts +11 -0
  226. package/dist/database/query/Relation.js +113 -0
  227. package/dist/database/query/ResolveQuery.d.ts +15 -0
  228. package/dist/database/query/ResolveQuery.js +154 -0
  229. package/dist/database/query/Search.d.ts +47 -0
  230. package/dist/database/query/Search.js +169 -0
  231. package/dist/database/sync/Derive.d.ts +8 -0
  232. package/dist/database/sync/Derive.js +270 -0
  233. package/dist/database/sync/EntryParser.d.ts +4 -0
  234. package/dist/database/sync/EntryParser.js +87 -0
  235. package/dist/database/sync/EntrySyncer.d.ts +37 -0
  236. package/dist/database/sync/EntrySyncer.js +117 -0
  237. package/dist/database/sync/Ingest.d.ts +21 -0
  238. package/dist/database/sync/Ingest.js +136 -0
  239. package/dist/database/sync/SyncQueries.d.ts +157 -0
  240. package/dist/database/sync/SyncQueries.js +192 -0
  241. package/dist/field/code/CodeField.view.js +12 -12
  242. package/dist/field/link/LinkField.js +23 -0
  243. package/dist/field/link/LinkField.view.js +41 -40
  244. package/dist/field/list/ListField.view.js +177 -155
  245. package/dist/field/metadata/MetadataField.d.ts +28 -3
  246. package/dist/field/metadata/MetadataField.js +44 -7
  247. package/dist/field/metadata/MetadataField.view.d.ts +5 -0
  248. package/dist/field/metadata/MetadataField.view.js +34 -19
  249. package/dist/field/richtext/RichTextBlock.js +2 -2
  250. package/dist/field/richtext/RichTextDocument.js +4 -3
  251. package/dist/field/richtext/RichTextExtensions.d.ts +1 -1
  252. package/dist/field/richtext/RichTextExtensions.js +32 -28
  253. package/dist/field/richtext/RichTextField.view.js +15 -6
  254. package/dist/field/views.js +2 -0
  255. package/dist/next.cjs +0 -18
  256. package/dist/next.d.ts +4 -3
  257. package/dist/next.edge.d.ts +1 -1
  258. package/dist/next.js +1 -2
  259. package/dist/picker/entry/EntryPicker.js +2 -1
  260. package/dist/picker/url/UrlPicker.js +1 -0
  261. package/dist/preview/widget.d.ts +16 -0
  262. package/dist/preview/widget.js +70 -1
  263. package/dist/query.d.ts +28 -0
  264. package/package.json +2 -1
  265. package/dist/backend/store/GeneratedSource.d.ts +0 -2
  266. package/dist/backend/store/GeneratedSource.js +0 -21
  267. package/dist/chunks/chunk-G6E35DMJ.js +0 -1997
  268. package/dist/core/db/EntryDB.d.ts +0 -19
  269. package/dist/core/db/EntryDB.js +0 -48
  270. package/dist/core/db/EntryIndex.d.ts +0 -143
  271. package/dist/core/db/EntryIndex.js +0 -2773
  272. package/dist/core/db/EntryReferenceIndex.d.ts +0 -21
  273. package/dist/core/db/EntryReferenceIndex.js +0 -131
  274. package/dist/core/db/EntryResolver.d.ts +0 -56
  275. package/dist/core/db/EntryResolver.js +0 -699
  276. package/dist/core/db/EntryTarget.d.ts +0 -5
  277. package/dist/core/db/EntryTransaction.d.ts +0 -31
  278. package/dist/core/db/EntryTransaction.js +0 -956
  279. package/dist/core/db/LocalDB.d.ts +0 -42
  280. package/dist/core/db/LocalDB.js +0 -127
  281. package/dist/core/db/TestDB.d.ts +0 -19
  282. package/dist/core/db/TestDB.js +0 -50
  283. /package/dist/core/db/{EntryTarget.js → LocalStore.js} +0 -0
@@ -1,956 +0,0 @@
1
- import "../../chunks/chunk-CQ52GEHY.js";
2
-
3
- // src/core/db/EntryTransaction.ts
4
- import { Config } from "alinea/core/Config";
5
- import { createRecord } from "alinea/core/EntryRecord";
6
- import { Field } from "alinea/core/Field";
7
- import { getRoot } from "alinea/core/Internal";
8
- import { ListRow } from "alinea/core/ListRow";
9
- import { Type } from "alinea/core/Type";
10
- import { ListEditor } from "alinea/core/field/ListField";
11
- import { MediaLocation } from "alinea/core/media/MediaLocation";
12
- import { entryUrl, pathSuffix } from "alinea/core/util/EntryFilenames";
13
- import {
14
- generateKeyBetween,
15
- generateNKeysBetween,
16
- isValidOrderKey
17
- } from "alinea/core/util/FractionalIndexing";
18
- import { entries, fromEntries, isRecord, keys } from "alinea/core/util/Objects";
19
- import * as paths from "alinea/core/util/Paths";
20
- import { slugify } from "alinea/core/util/Slugs";
21
- import { unreachable } from "alinea/core/util/Types";
22
- import { Permission, Policy } from "../Role.js";
23
- import { ShaMismatchError } from "../source/ShaMismatchError.js";
24
- import { OverlaySource } from "../source/OverlaySource.js";
25
- import { SourceTransaction } from "../source/Source.js";
26
- import { assert } from "../util/Assert.js";
27
- import { commitChanges } from "./CommitRequest.js";
28
- import { aliasesFromData, aliasUrl } from "./EntryAliases.js";
29
- import { EntryUrlConflictError } from "./EntryUrlConflictError.js";
30
- var EntryTransaction = class {
31
- #messages = [];
32
- #config;
33
- #index;
34
- #tx;
35
- #fileChanges = [];
36
- #policy;
37
- #urlClaims;
38
- #workingSource;
39
- #workingTree;
40
- constructor(config, index, source, from, policy = Policy.ALLOW_ALL) {
41
- if (index.sha !== from.sha) throw new ShaMismatchError(index.sha, from.sha);
42
- this.#config = config;
43
- this.#index = index.clone();
44
- this.#tx = new SourceTransaction(source, from);
45
- this.#policy = policy;
46
- this.#workingSource = new OverlaySource(source, from);
47
- this.#workingTree = from;
48
- }
49
- get empty() {
50
- return this.#messages.length === 0;
51
- }
52
- create({
53
- locale,
54
- type,
55
- data,
56
- root,
57
- workspace,
58
- fromSeed,
59
- parentId = null,
60
- id,
61
- insertOrder = "last",
62
- status = "published",
63
- overwrite = false
64
- }) {
65
- assert(id, "Create mutation is missing an id");
66
- const config = this.#config;
67
- const index = this.#index;
68
- const existing = index.byId(id);
69
- if (existing) {
70
- parentId = existing.parentId;
71
- if (!workspace) workspace = existing.workspace;
72
- if (!root) root = existing.root;
73
- assert(
74
- existing.workspace === workspace,
75
- `Cannot create entry with id ${id} in workspace ${workspace}, already exists in ${existing.workspace}`
76
- );
77
- assert(
78
- existing.root === root,
79
- `Cannot create entry with id ${id} in root ${root}, already exists in ${existing.root}`
80
- );
81
- }
82
- const workspaces = keys(config.workspaces);
83
- if (!workspace) workspace = workspaces[0];
84
- assert(
85
- workspace in config.workspaces,
86
- `Workspace "${workspace}" not found in config`
87
- );
88
- const roots = keys(config.workspaces[workspace]);
89
- if (!root) root = roots[0];
90
- assert(
91
- root in config.workspaces[workspace],
92
- `Root "${root}" not found in workspace "${workspace}"`
93
- );
94
- const rootConfig = this.#config.workspaces[workspace][root];
95
- assert(rootConfig, "Invalid root");
96
- this.#policy.assert(Permission.Create, {
97
- workspace,
98
- root,
99
- type
100
- });
101
- const i18n = getRoot(rootConfig).i18n;
102
- if (i18n) assert(i18n.locales.includes(locale), "Invalid locale");
103
- else assert(locale === null, "Invalid locale");
104
- let parent;
105
- if (parentId) {
106
- parent = index.findFirst((entry) => {
107
- return entry.id === parentId && entry.locale === locale && entry.main;
108
- });
109
- assert(parent, `Parent not found: ${parentId}`);
110
- this.#policy.assert(Permission.Create, parent);
111
- }
112
- const siblings = Array.from(
113
- index.findMany((entry) => {
114
- return entry.root === root && entry.workspace === workspace && entry.parentId === parentId;
115
- })
116
- );
117
- assert(typeof data === "object", "Invalid data");
118
- const title = data.title ?? data.path;
119
- assert(typeof title === "string", "Missing title");
120
- let path = slugify(typeof data.path === "string" ? data.path : title);
121
- assert(path.length > 0, "Invalid path");
122
- const existingPath = existing?.get(locale)?.path;
123
- const hasSamePath = existingPath === path;
124
- if (!hasSamePath)
125
- path = this.#getAvailablePath({
126
- id,
127
- path,
128
- parentId,
129
- root,
130
- workspace,
131
- locale
132
- });
133
- if (status !== "published" && existingPath) path = existingPath;
134
- if (existingPath && !hasSamePath && status === "published") {
135
- this.#rename(existing.id, locale, path);
136
- }
137
- if (overwrite && existing?.type === "MediaFile") {
138
- const prev = existing.get(null)?.main;
139
- assert(prev, "Previous entry not found");
140
- const prevLocation = prev.data.location;
141
- if (prevLocation !== data.location)
142
- this.removeFile({
143
- location: MediaLocation.storagePath(
144
- this.#config,
145
- prev.workspace,
146
- prev.data.location
147
- )
148
- });
149
- }
150
- const parentDir = parent ? parent.childrenDir : Config.filePath(this.#config, workspace, root, locale);
151
- const filePath = paths.join(
152
- parentDir,
153
- `${path}${status === "published" ? "" : `.${status}`}.json`
154
- );
155
- const hasSameVersion = existing?.get(locale)?.has(status);
156
- const warnDuplicate = !overwrite && hasSameVersion;
157
- assert(!warnDuplicate, `Cannot create duplicate entry with id ${id}`);
158
- let newIndex;
159
- if (existing) {
160
- newIndex = existing.index;
161
- if (status === "published") {
162
- const versions = index.byId(id)?.get(locale);
163
- if (versions)
164
- for (const [status2, version] of versions) {
165
- this.#tx.remove(version.filePath);
166
- }
167
- }
168
- } else {
169
- const previous = insertOrder === "first" ? null : siblings.at(-1) ?? null;
170
- const next = insertOrder === "last" ? null : siblings.at(0) ?? null;
171
- newIndex = generateKeyBetween(
172
- previous?.index ?? null,
173
- next?.index ?? null
174
- );
175
- }
176
- if (locale !== null && status === "published") {
177
- const from = index.findFirst((entry) => {
178
- return entry.id === id && entry.locale !== locale && entry.status === "published";
179
- });
180
- if (from) {
181
- const typeInstance = this.#config.schema[type];
182
- assert(typeInstance, `Type not found: ${type}`);
183
- const shared = Type.sharedData(typeInstance, from.data);
184
- data = { ...shared, ...data };
185
- }
186
- }
187
- if (status === "published") {
188
- const urlCandidate = {
189
- id,
190
- type,
191
- path,
192
- parentId,
193
- workspace,
194
- root,
195
- locale,
196
- data
197
- };
198
- data = this.#dataWithPreviousUrlAlias(
199
- urlCandidate,
200
- this.#publishedEntry(id, locale)
201
- );
202
- if (locale !== null) this.#persistSharedFields(id, locale, type, data);
203
- this.#assertUniqueUrls({ ...urlCandidate, data });
204
- }
205
- const seeds = existing?.get(locale);
206
- const seeded = fromSeed ?? seeds?.seeded ?? null;
207
- const record = createRecord(
208
- { id, type, index: newIndex, path, seeded, data, title },
209
- status
210
- );
211
- if (fromSeed && data.title === void 0) delete record.title;
212
- const contents = new TextEncoder().encode(JSON.stringify(record, null, 2));
213
- this.#tx.add(filePath, contents);
214
- this.#messages.push(this.#reportOp("create", title));
215
- return this;
216
- }
217
- #rename(entryId, locale, path) {
218
- const index = this.#index;
219
- const versions = index.findMany((entry) => {
220
- return entry.id === entryId && entry.locale === locale;
221
- });
222
- for (const version of versions) {
223
- const name = version.status === "published" ? path : `${path}.${version.status}`;
224
- const filePath = paths.join(version.parentDir, `${name}.json`);
225
- this.#tx.rename(version.filePath, filePath);
226
- const childrenDir = paths.join(version.parentDir, path);
227
- this.#tx.rename(version.childrenDir, childrenDir);
228
- }
229
- }
230
- update({ id, locale, status, set }) {
231
- const index = this.#index;
232
- const entry = index.findFirst((entry2) => {
233
- return entry2.id === id && entry2.locale === locale && entry2.status === status;
234
- });
235
- assert(entry, `Entry not found: ${id}`);
236
- this.#policy.assert(Permission.Update, entry);
237
- for (const key of keys(set)) {
238
- this.#policy.assert(Permission.Update, {
239
- workspace: entry.workspace,
240
- root: entry.root,
241
- type: entry.type,
242
- id: entry.id,
243
- parents: entry.parents,
244
- locale: entry.locale,
245
- field: key
246
- });
247
- }
248
- const fieldUpdates = fromEntries(
249
- entries(set).map(([key, value]) => {
250
- return [key, value ?? null];
251
- })
252
- );
253
- let data = { ...entry.data, ...fieldUpdates };
254
- const desiredPath = slugify(
255
- data.path ?? entry.data.path ?? entry.path
256
- );
257
- const lockPath = entry.status !== "published" && !entry.main;
258
- const path = lockPath ? entry.path : this.#getAvailablePath({
259
- id,
260
- path: desiredPath,
261
- parentId: entry.parentId,
262
- root: entry.root,
263
- workspace: entry.workspace,
264
- locale
265
- });
266
- const childrenDir = paths.join(entry.parentDir, path);
267
- const filePath = `${childrenDir}${entry.status === "published" ? "" : `.${entry.status}`}.json`;
268
- if (entry.status === "published") {
269
- this.#policy.assert(Permission.Publish, entry);
270
- if (filePath !== entry.filePath) this.#rename(id, locale, path);
271
- const urlCandidate = {
272
- id,
273
- type: entry.type,
274
- path,
275
- parentId: entry.parentId,
276
- root: entry.root,
277
- workspace: entry.workspace,
278
- locale,
279
- data
280
- };
281
- data = this.#dataWithPreviousUrlAlias(urlCandidate, entry);
282
- if (locale !== null)
283
- this.#persistSharedFields(id, locale, entry.type, data);
284
- this.#assertUniqueUrls({ ...urlCandidate, data });
285
- }
286
- const record = createRecord(
287
- {
288
- id,
289
- type: entry.type,
290
- index: entry.index,
291
- path: entry.path,
292
- seeded: entry.seeded,
293
- data
294
- },
295
- status
296
- );
297
- if (entry.status !== "published" && path !== entry.path) record.path = path;
298
- const contents = new TextEncoder().encode(JSON.stringify(record, null, 2));
299
- this.#tx.add(filePath, contents);
300
- this.#messages.push(this.#reportOp("update", entry.title));
301
- return this;
302
- }
303
- #getAvailablePath(target) {
304
- const conflictingPaths = Array.from(
305
- this.#index.findMany((entry) => {
306
- return entry.id !== target.id && entry.parentId === target.parentId && entry.workspace === target.workspace && entry.root === target.root && entry.locale === target.locale && (entry.path === target.path || entry.path.startsWith(`${target.path}-`));
307
- })
308
- ).map((entry) => entry.path);
309
- const suffix = pathSuffix(target.path, conflictingPaths);
310
- if (suffix !== void 0) return `${target.path}-${suffix}`;
311
- return target.path;
312
- }
313
- #assertUniqueUrls(candidate) {
314
- const claims = this.#getUrlClaims();
315
- const url = this.#resolvedUrl(candidate);
316
- const key = this.#urlClaimKey(
317
- candidate.workspace,
318
- candidate.root,
319
- candidate.type,
320
- url
321
- );
322
- const existing = claims.get(key);
323
- if (existing && existing.id !== candidate.id) {
324
- throw new EntryUrlConflictError({
325
- url,
326
- entryId: existing.id,
327
- workspace: existing.workspace,
328
- root: existing.root
329
- });
330
- }
331
- for (const [claimedKey, claim] of claims) {
332
- if (claim.id === candidate.id) claims.delete(claimedKey);
333
- }
334
- claims.set(key, {
335
- id: candidate.id,
336
- url,
337
- workspace: candidate.workspace,
338
- root: candidate.root
339
- });
340
- }
341
- #resolvedUrl(candidate) {
342
- const type = this.#config.schema[candidate.type];
343
- assert(type, `Type not found: ${candidate.type}`);
344
- if (candidate.url !== void 0) return candidate.url;
345
- return entryUrl(type, {
346
- config: this.#config,
347
- path: candidate.path,
348
- parentPaths: candidate.parentPaths ?? this.#parentPaths(candidate.parentId, candidate.locale),
349
- locale: candidate.locale,
350
- status: "published",
351
- workspace: candidate.workspace,
352
- root: candidate.root,
353
- data: candidate.data
354
- });
355
- }
356
- #dataWithPreviousUrlAlias(candidate, previousEntry) {
357
- if (!previousEntry) return candidate.data;
358
- const previousUrl = this.#resolvedUrl(previousEntry);
359
- const currentUrl = this.#resolvedUrl(candidate);
360
- if (previousUrl === currentUrl) return candidate.data;
361
- const type = this.#config.schema[candidate.type];
362
- assert(type, `Type not found: ${candidate.type}`);
363
- return dataWithUrlAlias(type, candidate.data, previousUrl, currentUrl);
364
- }
365
- #publishedEntry(id, locale) {
366
- return this.#index.findFirst((entry) => {
367
- return entry.id === id && entry.locale === locale && entry.status === "published";
368
- });
369
- }
370
- #parentPaths(parentId, locale) {
371
- const result = Array();
372
- let current = parentId ? this.#index.byId(parentId) : void 0;
373
- while (current) {
374
- const language = current.get(locale);
375
- assert(language, `Missing parent language node`);
376
- result.unshift(language.main.path);
377
- current = current.parent ?? void 0;
378
- }
379
- return result;
380
- }
381
- #getUrlClaims() {
382
- if (this.#urlClaims) return this.#urlClaims;
383
- const claims = /* @__PURE__ */ new Map();
384
- for (const entry of this.#index.findMany((entry2) => {
385
- return entry2.status === "published";
386
- })) {
387
- const url = this.#resolvedUrl(entry);
388
- claims.set(
389
- this.#urlClaimKey(entry.workspace, entry.root, entry.type, url),
390
- {
391
- id: entry.id,
392
- url,
393
- workspace: entry.workspace,
394
- root: entry.root
395
- }
396
- );
397
- }
398
- this.#urlClaims = claims;
399
- return claims;
400
- }
401
- #urlClaimKey(workspace, root, type, url) {
402
- if (type === "MediaFile") return `MediaFile\0${url}`;
403
- return `${workspace}\0${root}\0${url}`;
404
- }
405
- #persistSharedFields(id, locale, type, data) {
406
- const index = this.#index;
407
- const typeInstance = this.#config.schema[type];
408
- assert(type, `Type not found: ${type}`);
409
- const shared = Type.sharedData(typeInstance, data);
410
- if (shared) {
411
- const translations = index.findMany((entry) => {
412
- return entry.id === id && entry.locale !== locale;
413
- });
414
- for (const translation of translations) {
415
- const record = createRecord(
416
- {
417
- id,
418
- type: translation.type,
419
- index: translation.index,
420
- path: translation.path,
421
- seeded: translation.seeded,
422
- data: {
423
- ...translation.data,
424
- ...shared
425
- }
426
- },
427
- translation.status
428
- );
429
- const contents = new TextEncoder().encode(
430
- JSON.stringify(record, null, 2)
431
- );
432
- this.#tx.add(translation.filePath, contents);
433
- }
434
- }
435
- }
436
- #moveUrlAliasUpdates(target) {
437
- const updates = Array();
438
- for (const entry of this.#index.findMany((entry2) => {
439
- return entry2.status === "published" && (entry2.id === target.id || entry2.parents.includes(target.id));
440
- })) {
441
- const parentPaths = this.#movedParentPaths(entry, target);
442
- const candidate = {
443
- id: entry.id,
444
- type: entry.type,
445
- path: entry.path,
446
- parentId: entry.id === target.id ? target.parentId : entry.parentId,
447
- parentPaths,
448
- workspace: target.workspace,
449
- root: target.root,
450
- locale: entry.locale,
451
- data: entry.data
452
- };
453
- const data = this.#dataWithPreviousUrlAlias(candidate, entry);
454
- this.#assertUniqueUrls({ ...candidate, data });
455
- updates.push({
456
- entry,
457
- data,
458
- filePath: this.#movedFilePath(entry, target)
459
- });
460
- }
461
- return updates;
462
- }
463
- #movedParentPaths(entry, target) {
464
- const moved = this.#index.byId(target.id);
465
- assert(moved, `Entry not found: ${target.id}`);
466
- const movedLanguage = moved.get(entry.locale);
467
- assert(movedLanguage, `Missing moved entry language node`);
468
- const newParentPaths = this.#parentPaths(target.parentId, entry.locale);
469
- if (entry.id === target.id) return newParentPaths;
470
- const currentParentPaths = this.#parentPaths(entry.parentId, entry.locale);
471
- const oldPrefix = this.#parentPaths(moved.parentId, entry.locale).concat(
472
- movedLanguage.main.path
473
- );
474
- const newPrefix = newParentPaths.concat(movedLanguage.main.path);
475
- assert(
476
- startsWithSegments(currentParentPaths, oldPrefix),
477
- `Moved child is outside moved entry path`
478
- );
479
- return newPrefix.concat(currentParentPaths.slice(oldPrefix.length));
480
- }
481
- #movedFilePath(entry, target) {
482
- const moved = this.#index.byId(target.id);
483
- assert(moved, `Entry not found: ${target.id}`);
484
- const movedLanguage = moved.get(entry.locale);
485
- assert(movedLanguage, `Missing moved entry language node`);
486
- const parent = target.parentId ? this.#index.findFirst((parentEntry) => {
487
- return parentEntry.id === target.parentId && parentEntry.locale === entry.locale;
488
- }) : void 0;
489
- const parentDir = parent ? parent.childrenDir : Config.filePath(
490
- this.#config,
491
- target.workspace,
492
- target.root,
493
- entry.locale
494
- );
495
- const nextPrefix = paths.join(parentDir, movedLanguage.main.path);
496
- if (entry.id === target.id) {
497
- return `${nextPrefix}${entry.status === "published" ? "" : `.${entry.status}`}.json`;
498
- }
499
- const previousPrefix = movedLanguage.main.childrenDir;
500
- assert(
501
- entry.filePath === previousPrefix || entry.filePath.startsWith(`${previousPrefix}/`),
502
- `Moved child file is outside moved entry directory`
503
- );
504
- const suffix = entry.filePath.slice(previousPrefix.length).replace(/^\//, "");
505
- return paths.join(nextPrefix, suffix);
506
- }
507
- publish({ id, locale, status }) {
508
- const index = this.#index;
509
- const entry = index.findFirst((entry2) => {
510
- return entry2.id === id && entry2.locale === locale && entry2.status === status;
511
- });
512
- assert(entry, `Entry not found: ${id}`);
513
- this.#policy.assert(Permission.Publish, entry);
514
- const pathChange = entry.data.path && entry.data.path !== entry.path;
515
- let path = slugify(entry.data.path ?? entry.path);
516
- let data = entry.data;
517
- path = this.#getAvailablePath({
518
- id,
519
- path,
520
- parentId: entry.parentId,
521
- root: entry.root,
522
- workspace: entry.workspace,
523
- locale
524
- });
525
- const childrenDir = paths.join(entry.parentDir, path);
526
- const urlCandidate = {
527
- id,
528
- type: entry.type,
529
- path,
530
- parentId: entry.parentId,
531
- root: entry.root,
532
- workspace: entry.workspace,
533
- locale,
534
- data
535
- };
536
- data = this.#dataWithPreviousUrlAlias(
537
- urlCandidate,
538
- this.#publishedEntry(id, locale)
539
- );
540
- if (entry.locale !== null)
541
- this.#persistSharedFields(id, entry.locale, entry.type, data);
542
- this.#assertUniqueUrls({ ...urlCandidate, data });
543
- const versions = index.byId(id)?.get(locale);
544
- if (versions)
545
- for (const [_, version] of versions) {
546
- this.#tx.remove(version.filePath);
547
- }
548
- this.#tx.remove(entry.filePath);
549
- const record = createRecord({ ...entry, path, data }, "published");
550
- const contents = new TextEncoder().encode(JSON.stringify(record, null, 2));
551
- if (pathChange) {
552
- this.#tx.remove(`${entry.parentDir}/${entry.path}.json`);
553
- this.#tx.rename(entry.childrenDir, childrenDir);
554
- }
555
- this.#tx.add(`${childrenDir}.json`, contents);
556
- this.#messages.push(this.#reportOp("publish", entry.title));
557
- return this;
558
- }
559
- unpublish({ id, locale }) {
560
- const index = this.#index;
561
- const versions = index.byId(id)?.get(locale);
562
- const mainEntry = versions?.main;
563
- assert(mainEntry, `Entry not found: ${id}`);
564
- this.#policy.assert(Permission.Publish, mainEntry);
565
- for (const [_, version] of versions) {
566
- if (version === mainEntry) continue;
567
- this.#tx.remove(version.filePath);
568
- }
569
- this.#tx.rename(mainEntry.filePath, `${mainEntry.childrenDir}.draft.json`);
570
- this.#messages.push(this.#reportOp("unpublish", mainEntry.title));
571
- return this;
572
- }
573
- archive({ id, locale }) {
574
- const index = this.#index;
575
- const versions = index.byId(id)?.get(locale);
576
- const mainEntry = versions?.main;
577
- assert(mainEntry, `Entry not found: ${id}`);
578
- this.#policy.assert(Permission.Archive, mainEntry);
579
- for (const [_, version] of versions) {
580
- if (version === mainEntry) continue;
581
- this.#tx.remove(version.filePath);
582
- }
583
- this.#tx.rename(
584
- mainEntry.filePath,
585
- `${mainEntry.childrenDir}.archived.json`
586
- );
587
- this.#messages.push(this.#reportOp("archive", mainEntry.title));
588
- return this;
589
- }
590
- move({ id, target, dropPosition, targetType }) {
591
- const index = this.#index;
592
- targetType ??= "entry";
593
- const entries2 = Array.from(index.findMany((entry) => entry.id === id));
594
- assert(entries2.length > 0, `Entry not found: ${id}`);
595
- const targetEntry = targetType === "root" ? void 0 : index.findFirst((entry) => {
596
- return entry.id === target;
597
- });
598
- assert(targetType === "root" || targetEntry, `Target not found: ${target}`);
599
- assert(
600
- targetType === "entry" || dropPosition === "on",
601
- `Cannot move ${dropPosition} root ${target}`
602
- );
603
- const parentId = targetType === "root" ? null : dropPosition === "on" ? target : targetEntry?.parentId ?? null;
604
- const root = targetType === "root" ? target : targetEntry?.root ?? entries2[0].root;
605
- const workspace = targetEntry?.workspace ?? entries2[0].workspace;
606
- const action = parentId !== entries2[0].parentId || root !== entries2[0].root ? Permission.Move : Permission.Reorder;
607
- for (const entry of entries2) this.#policy.assert(action, entry);
608
- if (action === Permission.Move && parentId === null) {
609
- this.#policy.assert(Permission.Move, { workspace, root });
610
- }
611
- const siblingList = Array.from(
612
- index.findMany((entry) => {
613
- return entry.workspace === workspace && entry.root === root && entry.parentId === parentId && entry.id !== id;
614
- })
615
- );
616
- let insertIndex = siblingList.length;
617
- if (targetType !== "root") {
618
- const targetIndex = siblingList.findIndex((entry) => entry.id === target);
619
- if (dropPosition === "before") {
620
- assert(targetIndex >= 0, `Sibling not found: ${target}`);
621
- insertIndex = targetIndex;
622
- } else if (dropPosition === "after") {
623
- assert(targetIndex >= 0, `Sibling not found: ${target}`);
624
- insertIndex = targetIndex + 1;
625
- }
626
- }
627
- const previous = siblingList[insertIndex - 1] ?? null;
628
- const next = siblingList[insertIndex] ?? null;
629
- const seen = /* @__PURE__ */ new Set();
630
- const hasDuplicates = siblingList.some((entry) => {
631
- const wasSeen = seen.has(entry.index);
632
- if (wasSeen) return true;
633
- seen.add(entry.index);
634
- return false;
635
- });
636
- let newIndex;
637
- if (hasDuplicates) {
638
- const self = index.findFirst((entry) => entry.id === id);
639
- assert(self, `Entry not found: ${id}`);
640
- siblingList.splice(insertIndex, 0, self);
641
- for (const sibling of siblingList) {
642
- this.#policy.assert(Permission.Reorder, sibling);
643
- }
644
- const newKeys = generateNKeysBetween(null, null, siblingList.length);
645
- for (const [i, key] of newKeys.entries()) {
646
- const id2 = siblingList[i].id;
647
- const node = index.byId(id2);
648
- assert(node);
649
- for (const locale of node.keys()) {
650
- for (const [_, version] of node.get(locale)) {
651
- const record = createRecord(
652
- {
653
- id: id2,
654
- type: version.type,
655
- index: key,
656
- path: version.path,
657
- seeded: version.seeded,
658
- data: version.data
659
- },
660
- version.status
661
- );
662
- const contents = new TextEncoder().encode(
663
- JSON.stringify(record, null, 2)
664
- );
665
- this.#tx.add(version.filePath, contents);
666
- }
667
- }
668
- }
669
- newIndex = newKeys[insertIndex];
670
- } else {
671
- newIndex = generateKeyBetween(
672
- previous?.index ?? null,
673
- next?.index ?? null
674
- );
675
- }
676
- const moveUrlAliasUpdates = action === Permission.Move ? this.#moveUrlAliasUpdates({ id, parentId, workspace, root }) : [];
677
- const moveUrlAliasData = new Map(
678
- moveUrlAliasUpdates.map((update) => [update.entry.filePath, update.data])
679
- );
680
- let info;
681
- for (const entry of entries2) {
682
- info = entry;
683
- const parent = parentId ? index.findFirst((e) => {
684
- return e.id === parentId && e.locale === entry.locale;
685
- }) : void 0;
686
- if (action === Permission.Move && parentId) {
687
- assert(!entry.seeded, `Cannot move seeded entry ${entry.filePath}`);
688
- assert(parent, `Parent not found: ${parentId}`);
689
- this.#policy.assert(Permission.Move, parent);
690
- assert(
691
- !parent?.childrenDir.startsWith(entry.childrenDir),
692
- "Cannot move entry into its own children"
693
- );
694
- const parentType = this.#config.schema[parent.type];
695
- const childType = this.#config.schema[entry.type];
696
- const allowed = Config.typeContains(this.#config, parentType, childType);
697
- assert(
698
- allowed,
699
- `Parent of type ${parent.type} does not allow children of type ${entry.type}`
700
- );
701
- }
702
- const parentDir = parent ? parent.childrenDir : Config.filePath(this.#config, workspace, root, entry.locale);
703
- const childrenDir = paths.join(parentDir, entry.path);
704
- const filePath = `${childrenDir}${entry.status === "published" ? "" : `.${entry.status}`}.json`;
705
- const data = moveUrlAliasData.get(entry.filePath) ?? entry.data;
706
- const record = createRecord(
707
- {
708
- id,
709
- type: entry.type,
710
- index: newIndex,
711
- path: entry.path,
712
- seeded: entry.seeded,
713
- root,
714
- workspace,
715
- parentId,
716
- data
717
- },
718
- entry.status
719
- );
720
- const contents = new TextEncoder().encode(JSON.stringify(record, null, 2));
721
- if (action === Permission.Move) {
722
- this.#tx.remove(entry.filePath);
723
- this.#tx.rename(entry.childrenDir, childrenDir);
724
- }
725
- this.#tx.add(filePath, contents);
726
- }
727
- for (const update of moveUrlAliasUpdates) {
728
- const entry = update.entry;
729
- if (entry.id === id || update.data === entry.data) continue;
730
- const record = createRecord(
731
- {
732
- ...entry,
733
- root,
734
- workspace,
735
- data: update.data
736
- },
737
- entry.status
738
- );
739
- const contents = new TextEncoder().encode(JSON.stringify(record, null, 2));
740
- this.#tx.add(update.filePath, contents);
741
- }
742
- this.#messages.push(this.#reportOp("move", info.title));
743
- return this;
744
- }
745
- remove({ id, locale, status }) {
746
- const index = this.#index;
747
- const entries2 = index.findMany((entry) => {
748
- const matchesStatus = status === void 0 || entry.status === status;
749
- const matchesLocale = locale === void 0 || entry.locale === locale;
750
- return entry.id === id && matchesLocale && matchesStatus;
751
- });
752
- let info;
753
- for (const entry of entries2) {
754
- if (entry.status === "published")
755
- assert(!entry.seeded, `Cannot remove seeded entry ${entry.filePath}`);
756
- info = entry;
757
- this.#tx.remove(entry.filePath);
758
- if (entry.status !== "draft") {
759
- this.#tx.remove(entry.childrenDir);
760
- }
761
- if (entry.type === "MediaLibrary") {
762
- const files = index.findMany((f) => {
763
- return f.workspace === entry.workspace && f.root === entry.root && f.filePath.startsWith(entry.childrenDir) && f.type === "MediaFile";
764
- });
765
- for (const file of files) {
766
- this.removeFile({
767
- location: MediaLocation.storagePath(
768
- this.#config,
769
- entry.workspace,
770
- file.data.location
771
- )
772
- });
773
- }
774
- }
775
- if (entry.type === "MediaFile") {
776
- this.removeFile({
777
- location: MediaLocation.storagePath(
778
- this.#config,
779
- entry.workspace,
780
- entry.data.location
781
- )
782
- });
783
- }
784
- }
785
- if (info) {
786
- this.#policy.assert(Permission.Delete, info);
787
- this.#messages.unshift(this.#reportOp("remove", info.title));
788
- }
789
- return this;
790
- }
791
- removeFile(mutation) {
792
- this.#policy.assert(Permission.Delete);
793
- assert(mutation.location, "Missing location");
794
- this.#messages.push(this.#reportOp("remove", mutation.location));
795
- this.#fileChanges.push({ op: "removeFile", ...mutation });
796
- return this;
797
- }
798
- uploadFile(mutation) {
799
- this.#policy.assert(Permission.Upload);
800
- this.#fileChanges.push({ op: "uploadFile", ...mutation });
801
- return this;
802
- }
803
- description() {
804
- return this.#messages.map((message, index, all) => {
805
- if (index > 0) return message;
806
- const suffix = all.length > 1 ? ` (and ${all.length - 1} other edits)` : "";
807
- return `${message + suffix}`;
808
- }).join("\n");
809
- }
810
- #reportOp(op, title) {
811
- return `(${op}) ${title}`;
812
- }
813
- async apply(mutations) {
814
- for (const mutation of mutations) {
815
- switch (mutation.op) {
816
- case "create":
817
- this.create(mutation);
818
- break;
819
- case "update":
820
- this.update(mutation);
821
- break;
822
- case "publish":
823
- this.publish(mutation);
824
- break;
825
- case "unpublish":
826
- this.unpublish(mutation);
827
- break;
828
- case "archive":
829
- this.archive(mutation);
830
- break;
831
- case "move":
832
- this.move(mutation);
833
- break;
834
- case "remove":
835
- this.remove(mutation);
836
- break;
837
- case "removeFile":
838
- this.removeFile(mutation);
839
- break;
840
- case "uploadFile":
841
- this.uploadFile(mutation);
842
- break;
843
- default:
844
- unreachable(mutation);
845
- }
846
- await this.#flush();
847
- }
848
- }
849
- async #flush() {
850
- const { from, into, changes } = await this.#tx.compile(this.#workingTree);
851
- const batch = {
852
- fromSha: from.sha,
853
- changes
854
- };
855
- if (batch.changes.length > 0) {
856
- await Promise.all([
857
- this.#workingSource.applyChanges(batch),
858
- this.#index.indexChanges(batch, into)
859
- ]);
860
- }
861
- this.#workingTree = into;
862
- this.#urlClaims = void 0;
863
- }
864
- async toRequest() {
865
- const { from, into, changes } = await this.#tx.compile();
866
- return {
867
- fromSha: from.sha,
868
- intoSha: into.sha,
869
- description: this.description(),
870
- changes: this.#fileChanges.concat(commitChanges(changes))
871
- };
872
- }
873
- };
874
- function aliasUrlsFromData(data) {
875
- const result = /* @__PURE__ */ new Set();
876
- for (const alias of aliasesFromData(data) ?? []) {
877
- const url = aliasUrl(alias);
878
- if (url) result.add(url);
879
- }
880
- return Array.from(result);
881
- }
882
- function dataWithUrlAlias(type, data, previousUrl, currentUrl) {
883
- const target = typeAliasTarget(type);
884
- if (!target) return data;
885
- const aliasUrls = aliasUrlsFromData(data);
886
- if (aliasUrls.includes(previousUrl)) return data;
887
- const nextData = aliasUrls.includes(currentUrl) ? dataWithoutUrlAlias(data, currentUrl) : data;
888
- const metadata = isRecord(nextData.metadata) ? nextData.metadata : {};
889
- const aliases = target === "metadata" ? Array.isArray(metadata.aliases) ? metadata.aliases : [] : Array.isArray(nextData.aliases) ? nextData.aliases : [];
890
- return dataWithAliases(
891
- nextData,
892
- target,
893
- metadata,
894
- aliases.concat(createUrlAliasRow(previousUrl, aliases))
895
- );
896
- }
897
- function dataWithoutUrlAlias(data, url) {
898
- let result = data;
899
- if (Array.isArray(data.aliases)) {
900
- result = {
901
- ...result,
902
- aliases: data.aliases.filter((alias) => aliasUrl(alias) !== url)
903
- };
904
- }
905
- const metadata = data.metadata;
906
- if (isRecord(metadata) && Array.isArray(metadata.aliases)) {
907
- result = {
908
- ...result,
909
- metadata: {
910
- ...metadata,
911
- aliases: metadata.aliases.filter((alias) => aliasUrl(alias) !== url)
912
- }
913
- };
914
- }
915
- return result;
916
- }
917
- function dataWithAliases(data, target, metadata, aliases) {
918
- if (target === "aliases") return { ...data, aliases };
919
- return {
920
- ...data,
921
- metadata: {
922
- ...metadata,
923
- aliases
924
- }
925
- };
926
- }
927
- function typeAliasTarget(type) {
928
- if (Type.field(type, "aliases")) return "aliases";
929
- const metadata = Type.field(type, "metadata");
930
- if (!metadata) return void 0;
931
- const options = Field.options(metadata);
932
- const fields = options.fields;
933
- if (!Type.isType(fields)) return void 0;
934
- return Type.field(fields, "aliases") ? "metadata" : void 0;
935
- }
936
- function createUrlAliasRow(url, aliases) {
937
- const orderedAliases = aliases.filter(isOrderedUrlAliasRow);
938
- const editor = new ListEditor(orderedAliases);
939
- const created = editor.add("alias", { url }).value().at(-1);
940
- assert(created);
941
- return created;
942
- }
943
- function isOrderedUrlAliasRow(value) {
944
- if (!isRecord(value)) return false;
945
- const id = value[ListRow.id];
946
- const index = value[ListRow.index];
947
- const type = value[ListRow.type];
948
- const url = value.url;
949
- return typeof id === "string" && typeof index === "string" && isValidOrderKey(index) && type === "alias" && typeof url === "string";
950
- }
951
- function startsWithSegments(value, prefix) {
952
- return prefix.every((segment, index) => value[index] === segment);
953
- }
954
- export {
955
- EntryTransaction
956
- };