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.
- package/dist/.init/content/pages/welcome.json +1 -1
- package/dist/LICENSES.md +120 -120
- package/dist/adapter/next/cms.d.ts +18 -4
- package/dist/adapter/next/cms.js +125 -36
- package/dist/adapter/next/handler.d.ts +5 -1
- package/dist/adapter/next/handler.js +14 -7
- package/dist/adapter/next/node.d.ts +5 -0
- package/dist/adapter/next/node.js +16 -0
- package/dist/adapter/next/previews.d.ts +4 -1
- package/dist/adapter/next/previews.js +13 -1
- package/dist/adapter/next/renderStats.d.ts +24 -0
- package/dist/adapter/next/renderStats.js +74 -0
- package/dist/adapter/next/syncCheck.d.ts +8 -2
- package/dist/adapter/next/syncCheck.js +3 -3
- package/dist/adapter/next/with-alinea.js +0 -18
- package/dist/backend/Handler.d.ts +2 -2
- package/dist/backend/Handler.js +33 -12
- package/dist/backend/api/CreateBackend.js +15 -3
- package/dist/backend/api/DatabaseApi.js +22 -135
- package/dist/backend/api/database.js +15 -3
- package/dist/backend/resolver/ParsePreview.d.ts +2 -2
- package/dist/backend/router/NodeHandler.js +2 -0
- package/dist/backend/store/GeneratedDatabase.d.ts +5 -0
- package/dist/backend/store/GeneratedDatabase.js +31 -0
- package/dist/backend/store/GeneratedDatabaseNode.d.ts +4 -0
- package/dist/backend/store/GeneratedDatabaseNode.js +35 -0
- package/dist/backend/util/ORM.js +4 -2
- package/dist/bundled.d.ts +1470 -442
- package/dist/chunks/bun-sqlite-ZZDB7YQQ.js +18 -0
- package/dist/chunks/chunk-2BJ2IFQM.js +1085 -0
- package/dist/chunks/chunk-2PIFHEYR.js +364 -0
- package/dist/chunks/{chunk-X47OVDF3.js → chunk-2U43BB6P.js} +356 -1000
- package/dist/chunks/chunk-76VMZTLN.js +55 -0
- package/dist/chunks/chunk-A42DP5T7.js +94 -0
- package/dist/chunks/chunk-ATF42T2B.js +35 -0
- package/dist/chunks/{chunk-NWQXN4SB.js → chunk-ETBQTP2S.js} +40 -29
- package/dist/chunks/chunk-IL7FJQAY.js +94 -0
- package/dist/chunks/chunk-K62JP6HR.js +22 -0
- package/dist/chunks/chunk-K7LHQXI4.js +103 -0
- package/dist/chunks/chunk-M7KDJEGH.js +495 -0
- package/dist/chunks/chunk-N47Y62CI.js +396 -0
- package/dist/chunks/chunk-NLZLRQ5U.js +100 -0
- package/dist/chunks/chunk-QBPYWU6C.js +24 -0
- package/dist/chunks/{chunk-6P4UVRPH.js → chunk-RWWYUNVX.js} +2 -1
- package/dist/chunks/chunk-TFMUSWGG.js +605 -0
- package/dist/chunks/{chunk-I4GC3E65.js → chunk-UPY2RUZF.js} +0 -1
- package/dist/chunks/chunk-XCZXR3C2.js +112 -0
- package/dist/chunks/chunk-XTWYBWP2.js +81 -0
- package/dist/chunks/node-sqlite-7CSGL27V.js +18 -0
- package/dist/cli/Generate.js +74 -64
- package/dist/cli/Serve.js +1 -1
- package/dist/cli/bin.js +2 -1
- package/dist/cli/generate/CopyStaticFiles.js +17 -14
- package/dist/cli/generate/DevDB.d.ts +22 -5
- package/dist/cli/generate/DevDB.js +136 -24
- package/dist/cli/serve/CreateLocalServer.js +22 -4
- package/dist/cli/serve/LiveReload.d.ts +1 -1
- package/dist/cli/serve/LiveReload.js +3 -0
- package/dist/cloud/CloudRemote.js +2 -1
- package/dist/components/Calendar.js +6 -6
- package/dist/components/Checkbox.js +3 -3
- package/dist/components/ColorSwatchPicker.js +2 -2
- package/dist/components/ComboBox.js +9 -9
- package/dist/components/DateField.js +1 -1
- package/dist/components/DatePicker.js +6 -6
- package/dist/components/DateRangePicker.js +6 -6
- package/dist/components/Disclosure.js +9 -9
- package/dist/components/Label.js +5 -5
- package/dist/components/List.js +33 -33
- package/dist/components/Menu.js +3 -3
- package/dist/components/Modal.js +3 -3
- package/dist/components/MultipleSelect.js +25 -25
- package/dist/components/NumberField.js +6 -6
- package/dist/components/ProgressCircle.js +3 -3
- package/dist/components/RadioGroup.js +2 -2
- package/dist/components/SearchField.js +5 -5
- package/dist/components/Select.js +7 -7
- package/dist/components/Surface.js +2 -2
- package/dist/components/Table.js +6 -6
- package/dist/components/Tabs.js +2 -2
- package/dist/components/TextField.js +6 -6
- package/dist/components/Toggle.js +2 -2
- package/dist/components/Toolbar.js +2 -2
- package/dist/components/Tooltip.js +4 -4
- package/dist/components/Tree.d.ts +2 -1
- package/dist/components/Tree.js +5 -5
- package/dist/core/Document.js +15 -3
- package/dist/core/Entry.d.ts +3 -0
- package/dist/core/Entry.js +4 -0
- package/dist/core/EntrySeed.d.ts +19 -0
- package/dist/core/EntrySeed.js +136 -0
- package/dist/core/Field.d.ts +7 -0
- package/dist/core/Field.js +4 -0
- package/dist/core/Filter.d.ts +40 -2
- package/dist/core/Filter.js +145 -0
- package/dist/core/Graph.d.ts +9 -0
- package/dist/core/ListRow.d.ts +1 -0
- package/dist/core/ListRow.js +1 -0
- package/dist/core/Reference.d.ts +1 -0
- package/dist/core/Root.js +1 -1
- package/dist/core/Scope.d.ts +2 -0
- package/dist/core/Scope.js +16 -1
- package/dist/core/Type.d.ts +2 -1
- package/dist/core/Type.js +13 -1
- package/dist/core/db/EntryAliases.js +1 -11
- package/dist/core/db/EntryReference.d.ts +0 -6
- package/dist/core/db/IndexEvent.d.ts +2 -1
- package/dist/core/db/IndexEvent.js +11 -3
- package/dist/core/db/LinkResolver.d.ts +6 -6
- package/dist/core/db/LinkResolver.js +0 -52
- package/dist/core/db/LocalStore.d.ts +25 -0
- package/dist/core/field/ListField.js +19 -0
- package/dist/core/field/RecordField.js +8 -0
- package/dist/core/field/RichTextField.js +42 -0
- package/dist/core/field/UnionField.js +8 -0
- package/dist/core/source/FSSource.d.ts +14 -2
- package/dist/core/source/FSSource.js +58 -13
- package/dist/core/source/GitUtils.d.ts +4 -0
- package/dist/core/source/GitUtils.js +9 -10
- package/dist/core/source/GithubSource.js +22 -11
- package/dist/core/source/IndexedDBSource.d.ts +2 -1
- package/dist/core/source/IndexedDBSource.js +67 -53
- package/dist/core/source/MemorySource.js +5 -3
- package/dist/core/source/OverlaySource.d.ts +5 -2
- package/dist/core/source/OverlaySource.js +31 -3
- package/dist/core/source/Source.d.ts +3 -0
- package/dist/core/source/Source.js +15 -2
- package/dist/core/source/Tree.d.ts +10 -7
- package/dist/core/source/Tree.js +118 -70
- package/dist/core/source/Utils.js +6 -3
- package/dist/core/util/Arrays.d.ts +1 -0
- package/dist/core/util/Arrays.js +10 -0
- package/dist/core/util/Async.d.ts +7 -0
- package/dist/core/util/Async.js +15 -0
- package/dist/core/util/Checks.js +1 -1
- package/dist/core/util/EntryFilenames.d.ts +22 -1
- package/dist/core/util/EntryFilenames.js +56 -2
- package/dist/core/util/IndexedDB.d.ts +4 -0
- package/dist/core/util/IndexedDB.js +20 -0
- package/dist/core/util/Paths.js +5 -1
- package/dist/dashboard/App.js +3 -3
- package/dist/dashboard/ViewKeys.d.ts +1 -0
- package/dist/dashboard/ViewKeys.js +1 -0
- package/dist/dashboard/app/AccessDenied.js +4 -4
- package/dist/dashboard/app/ActivityStatus.js +11 -11
- package/dist/dashboard/app/AppShell.js +2 -2
- package/dist/dashboard/app/AuthView.js +9 -9
- package/dist/dashboard/app/Badge.js +2 -2
- package/dist/dashboard/app/CompactField.js +10 -10
- package/dist/dashboard/app/DashboardErrorBoundary.js +6 -6
- package/dist/dashboard/app/EntryFields.js +2 -2
- package/dist/dashboard/app/EntryHeader.js +10 -10
- package/dist/dashboard/app/EntryReferences.js +8 -8
- package/dist/dashboard/app/EntrySidebar.js +8 -8
- package/dist/dashboard/app/EntrySidebarPreview.js +11 -11
- package/dist/dashboard/app/EntryTranslationBanner.js +6 -6
- package/dist/dashboard/app/Explorer.js +44 -44
- package/dist/dashboard/app/ExplorerCards.js +30 -30
- package/dist/dashboard/app/ExplorerFileCard.js +17 -17
- package/dist/dashboard/app/ExplorerList.js +14 -14
- package/dist/dashboard/app/ExplorerModal.js +5 -5
- package/dist/dashboard/app/ExplorerTable.js +47 -47
- package/dist/dashboard/app/LinkPicker.js +2 -2
- package/dist/dashboard/app/LocaleMenu.js +6 -6
- package/dist/dashboard/app/SidebarTree.js +76 -40
- package/dist/dashboard/app/WorkspaceMenu.js +10 -10
- package/dist/dashboard/app/WorkspaceRoots.js +27 -27
- package/dist/dashboard/app/editor/FileEditor.js +24 -24
- package/dist/dashboard/app/editor/FilePreview.js +6 -6
- package/dist/dashboard/app/modals/CreateEntry.js +4 -4
- package/dist/dashboard/app/pages/EntryPage.js +15 -15
- package/dist/dashboard/app/pages/RootPage.js +3 -3
- package/dist/dashboard/app/pages/SplashPage.js +47 -47
- package/dist/dashboard/app/pages/UsersPage.js +36 -36
- package/dist/dashboard/app/ui/DashboardModal.js +17 -17
- package/dist/dashboard/app/ui/Rail.js +4 -4
- package/dist/dashboard/atoms/config.d.ts +4 -4
- package/dist/dashboard/atoms/entry.d.ts +8 -9
- package/dist/dashboard/atoms/entry.js +34 -4
- package/dist/dashboard/atoms/explorer.d.ts +2 -0
- package/dist/dashboard/atoms/explorer.js +7 -2
- package/dist/dashboard/boot/Boot.js +2 -3
- package/dist/dashboard/boot/DashboardWorker.d.ts +3 -3
- package/dist/dashboard/boot/DashboardWorker.js +78 -27
- package/dist/dashboard/boot/LoadWorker.js +2 -3
- package/dist/dashboard/icons.d.ts +2 -0
- package/dist/dashboard/icons.js +40 -0
- package/dist/dashboard/plugins/alineaFixturePlugin.js +14 -3
- package/dist/database/BrowserEntryStore.d.ts +30 -0
- package/dist/database/BrowserEntryStore.js +168 -0
- package/dist/database/DatabaseSource.d.ts +13 -0
- package/dist/database/DatabaseSource.js +25 -0
- package/dist/database/DatabaseTables.d.ts +38 -0
- package/dist/database/DatabaseTables.js +53 -0
- package/dist/database/EntryDatabase.d.ts +21 -0
- package/dist/database/EntryDatabase.js +138 -0
- package/dist/database/EntryLayer.d.ts +93 -0
- package/dist/database/EntryLayer.js +459 -0
- package/dist/database/EntryStore.d.ts +60 -0
- package/dist/database/EntryStore.js +269 -0
- package/dist/database/EntryTransaction.d.ts +33 -0
- package/dist/database/EntryTransaction.js +889 -0
- package/dist/database/EntryUrlAliases.d.ts +2 -0
- package/dist/database/EntryUrlAliases.js +69 -0
- package/dist/database/LocalDB.d.ts +39 -0
- package/dist/database/LocalDB.js +79 -0
- package/dist/database/Version.d.ts +9 -0
- package/dist/database/Version.js +13 -0
- package/dist/database/driver/RuntimeDatabase.d.ts +8 -0
- package/dist/database/driver/RuntimeDatabase.js +33 -0
- package/dist/database/driver/WasmDatabase.d.ts +8 -0
- package/dist/database/driver/WasmDatabase.js +33 -0
- package/dist/database/entry/EntryData.d.ts +13 -0
- package/dist/database/entry/EntryData.js +51 -0
- package/dist/database/entry/EntryTable.d.ts +173 -0
- package/dist/database/entry/EntryTable.js +137 -0
- package/dist/database/entry/EntryView.d.ts +22 -0
- package/dist/database/entry/EntryView.js +106 -0
- package/dist/database/query/Condition.d.ts +8 -0
- package/dist/database/query/Condition.js +117 -0
- package/dist/database/query/EntryQuery.d.ts +43 -0
- package/dist/database/query/EntryQuery.js +414 -0
- package/dist/database/query/EntryReferences.d.ts +6 -0
- package/dist/database/query/EntryReferences.js +68 -0
- package/dist/database/query/Relation.d.ts +11 -0
- package/dist/database/query/Relation.js +113 -0
- package/dist/database/query/ResolveQuery.d.ts +15 -0
- package/dist/database/query/ResolveQuery.js +154 -0
- package/dist/database/query/Search.d.ts +47 -0
- package/dist/database/query/Search.js +169 -0
- package/dist/database/sync/Derive.d.ts +8 -0
- package/dist/database/sync/Derive.js +270 -0
- package/dist/database/sync/EntryParser.d.ts +4 -0
- package/dist/database/sync/EntryParser.js +87 -0
- package/dist/database/sync/EntrySyncer.d.ts +37 -0
- package/dist/database/sync/EntrySyncer.js +117 -0
- package/dist/database/sync/Ingest.d.ts +21 -0
- package/dist/database/sync/Ingest.js +136 -0
- package/dist/database/sync/SyncQueries.d.ts +157 -0
- package/dist/database/sync/SyncQueries.js +192 -0
- package/dist/field/code/CodeField.view.js +12 -12
- package/dist/field/link/LinkField.js +23 -0
- package/dist/field/link/LinkField.view.js +41 -40
- package/dist/field/list/ListField.view.js +177 -155
- package/dist/field/metadata/MetadataField.d.ts +28 -3
- package/dist/field/metadata/MetadataField.js +44 -7
- package/dist/field/metadata/MetadataField.view.d.ts +5 -0
- package/dist/field/metadata/MetadataField.view.js +34 -19
- package/dist/field/richtext/RichTextBlock.js +2 -2
- package/dist/field/richtext/RichTextDocument.js +4 -3
- package/dist/field/richtext/RichTextExtensions.d.ts +1 -1
- package/dist/field/richtext/RichTextExtensions.js +32 -28
- package/dist/field/richtext/RichTextField.view.js +15 -6
- package/dist/field/views.js +2 -0
- package/dist/next.cjs +0 -18
- package/dist/next.d.ts +4 -3
- package/dist/next.edge.d.ts +1 -1
- package/dist/next.js +1 -2
- package/dist/picker/entry/EntryPicker.js +2 -1
- package/dist/picker/url/UrlPicker.js +1 -0
- package/dist/preview/widget.d.ts +16 -0
- package/dist/preview/widget.js +70 -1
- package/dist/query.d.ts +28 -0
- package/package.json +2 -1
- package/dist/backend/store/GeneratedSource.d.ts +0 -2
- package/dist/backend/store/GeneratedSource.js +0 -21
- package/dist/chunks/chunk-G6E35DMJ.js +0 -1997
- package/dist/core/db/EntryDB.d.ts +0 -19
- package/dist/core/db/EntryDB.js +0 -48
- package/dist/core/db/EntryIndex.d.ts +0 -143
- package/dist/core/db/EntryIndex.js +0 -2773
- package/dist/core/db/EntryReferenceIndex.d.ts +0 -21
- package/dist/core/db/EntryReferenceIndex.js +0 -131
- package/dist/core/db/EntryResolver.d.ts +0 -56
- package/dist/core/db/EntryResolver.js +0 -699
- package/dist/core/db/EntryTarget.d.ts +0 -5
- package/dist/core/db/EntryTransaction.d.ts +0 -31
- package/dist/core/db/EntryTransaction.js +0 -956
- package/dist/core/db/LocalDB.d.ts +0 -42
- package/dist/core/db/LocalDB.js +0 -127
- package/dist/core/db/TestDB.d.ts +0 -19
- package/dist/core/db/TestDB.js +0 -50
- /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
|
-
};
|