alinea 2.0.0-preview.13 → 2.0.0-preview.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.init/content/pages/welcome.json +1 -1
- package/dist/.mcp/later/dev.json +1 -1
- package/dist/.mcp/online/dev.json +1 -1
- package/dist/adapter/next/cms.js +6 -2
- package/dist/adapter/next/context.js +1 -4
- package/dist/adapter/next/handler.js +12 -1
- package/dist/backend/CommitEntries.d.ts +17 -0
- package/dist/backend/CommitEntries.js +39 -0
- package/dist/backend/Handler.d.ts +9 -0
- package/dist/backend/Handler.js +80 -20
- package/dist/backend/api/BasicAuth.js +5 -1
- package/dist/backend/api/CreateBackend.js +2 -2
- package/dist/backend/api/DatabaseApi.js +18 -5
- package/dist/backend/api/GithubApi.d.ts +13 -0
- package/dist/backend/api/GithubApi.js +170 -30
- package/dist/backend/api/database.js +2 -2
- package/dist/backend/util/Syncable.d.ts +6 -0
- package/dist/backend/util/Syncable.js +3 -1
- package/dist/bundled.d.ts +911 -151
- package/dist/chunks/{chunk-5H4D5JQL.js → chunk-2PP7WJUJ.js} +1 -11
- package/dist/chunks/{chunk-QHHMKJUO.js → chunk-7DDPSWRY.js} +465 -2
- package/dist/chunks/{chunk-HOETF6HN.js → chunk-PMGIIYDD.js} +1 -0
- package/dist/chunks/{chunk-3QFSGTGF.js → chunk-SCV3TEXM.js} +1 -1
- package/dist/chunks/{chunk-UPY2RUZF.js → chunk-TWBREZWC.js} +30 -0
- package/dist/cli/Generate.js +20 -7
- package/dist/cli/Mcp.js +1 -1
- package/dist/cli/Serve.js +1 -1
- package/dist/cli/bin.js +1 -1
- package/dist/cli/build/BuildEmitter.d.ts +5 -1
- package/dist/cli/build/BuildEmitter.js +8 -3
- package/dist/cli/generate/CompileConfig.js +10 -6
- package/dist/cli/generate/DevDB.d.ts +4 -1
- package/dist/cli/generate/DevDB.js +15 -7
- package/dist/cli/generate/GenerateDashboard.js +1 -0
- package/dist/cli/generate/MediaLocations.d.ts +18 -0
- package/dist/cli/generate/MediaLocations.js +69 -0
- package/dist/cli/serve/CreateLocalServer.js +1 -0
- package/dist/cli/serve/mcp/DevMcp.js +1 -1
- package/dist/cloud/CloudRemote.js +21 -7
- package/dist/cms.d.ts +4 -2
- package/dist/components/Alert.js +2 -2
- package/dist/components/AppShell.js +2 -2
- package/dist/components/AsyncList.d.ts +52 -0
- package/dist/components/AsyncList.js +69 -0
- package/dist/components/Breadcrumbs.js +6 -6
- package/dist/components/Button.js +4 -4
- package/dist/components/Calendar.js +18 -18
- package/dist/components/Checkbox.js +10 -10
- package/dist/components/CheckboxGroup.js +2 -2
- package/dist/components/Collapsible.js +5 -5
- package/dist/components/ColorSwatch.js +2 -2
- package/dist/components/ColorSwatchPicker.js +4 -4
- package/dist/components/ComboBox.d.ts +27 -9
- package/dist/components/ComboBox.js +40 -9
- package/dist/components/Command.js +13 -13
- package/dist/components/ContentCard.js +28 -28
- package/dist/components/ContentGrid.js +11 -11
- package/dist/components/DataList.js +1 -1
- package/dist/components/DateField.d.ts +4 -0
- package/dist/components/DateField.js +2 -2
- package/dist/components/DatePicker.js +2 -2
- package/dist/components/DateRangePicker.js +2 -2
- package/dist/components/Dialog.d.ts +14 -3
- package/dist/components/Dialog.js +38 -11
- package/dist/components/DropZone.js +6 -6
- package/dist/components/DropdownMenu.js +8 -8
- package/dist/components/Empty.js +4 -4
- package/dist/components/Field.d.ts +1 -1
- package/dist/components/Field.js +18 -12
- package/dist/components/Form.d.ts +23 -0
- package/dist/components/Form.js +64 -0
- package/dist/components/Link.d.ts +8 -1
- package/dist/components/List.js +27 -27
- package/dist/components/MediaPreview.js +5 -5
- package/dist/components/MultipleSelect.d.ts +13 -8
- package/dist/components/MultipleSelect.js +25 -25
- package/dist/components/NavRail.js +7 -7
- package/dist/components/NumberField.d.ts +5 -1
- package/dist/components/NumberField.js +6 -4
- package/dist/components/Page.js +6 -6
- package/dist/components/Popover.js +2 -2
- package/dist/components/PreviewFrame.js +8 -8
- package/dist/components/RadioGroup.js +10 -10
- package/dist/components/Resizable.js +1 -1
- package/dist/components/SearchField.js +8 -8
- package/dist/components/Select.d.ts +13 -10
- package/dist/components/Select.js +18 -32
- package/dist/components/Sheet.js +6 -6
- package/dist/components/Sidebar.js +5 -5
- package/dist/components/SortableList.js +12 -12
- package/dist/components/Spinner.js +4 -4
- package/dist/components/Stack.d.ts +20 -0
- package/dist/components/Stack.js +45 -0
- package/dist/components/Surface.js +1 -1
- package/dist/components/Switch.js +5 -5
- package/dist/components/Table.d.ts +11 -1
- package/dist/components/Table.js +81 -53
- package/dist/components/Tabs.js +6 -6
- package/dist/components/TagGroup.js +10 -10
- package/dist/components/TextField.d.ts +7 -1
- package/dist/components/TextField.js +20 -11
- package/dist/components/TimeField.d.ts +6 -1
- package/dist/components/TimeField.js +51 -26
- package/dist/components/Timestamp.js +1 -1
- package/dist/components/ToggleGroup.js +2 -2
- package/dist/components/Toolbar.js +3 -3
- package/dist/components/Tooltip.js +5 -4
- package/dist/components/Tree.js +27 -27
- package/dist/components/internal/DateControl.js +11 -11
- package/dist/components/internal/FormScope.d.ts +2 -0
- package/dist/components/internal/FormScope.js +8 -0
- package/dist/components/internal/ListBoxGroup.d.ts +12 -0
- package/dist/components/internal/ListBoxGroup.js +46 -0
- package/dist/components/internal/ListBoxOption.d.ts +2 -2
- package/dist/components/internal/ListBoxOption.js +5 -5
- package/dist/components/internal/Locale.js +2 -2
- package/dist/components/internal/PopoverSurface.js +5 -4
- package/dist/components/internal/SelectionCheckbox.js +5 -5
- package/dist/components/internal/Slot.js +1 -1
- package/dist/components/internal/ToggleButton.js +4 -4
- package/dist/components/internal/Trigger.js +2 -2
- package/dist/components/internal/useDragDrop.js +2 -2
- package/dist/components/types.d.ts +4 -0
- package/dist/components.d.ts +3 -0
- package/dist/components.js +3 -0
- package/dist/core/Client.js +12 -1
- package/dist/core/Config.d.ts +6 -0
- package/dist/core/Config.js +12 -0
- package/dist/core/DashboardEntry.d.ts +19 -0
- package/dist/core/DashboardEntry.js +0 -0
- package/dist/core/Document.d.ts +2 -2
- package/dist/core/Document.js +10 -3
- package/dist/core/EntryFields.d.ts +8 -2
- package/dist/core/Field.d.ts +5 -0
- package/dist/core/Filter.d.ts +19 -2
- package/dist/core/Filter.js +5 -0
- package/dist/core/Graph.d.ts +10 -2
- package/dist/core/Overview.d.ts +74 -5
- package/dist/core/Overview.js +1 -0
- package/dist/core/Root.js +1 -1
- package/dist/core/Scope.d.ts +5 -0
- package/dist/core/Scope.js +22 -5
- package/dist/core/Type.d.ts +25 -6
- package/dist/core/Type.js +25 -7
- package/dist/core/db/LocalStore.d.ts +7 -0
- package/dist/core/field/CreateField.d.ts +1 -2
- package/dist/core/source/Source.d.ts +2 -0
- package/dist/core/source/Source.js +11 -0
- package/dist/core/util/Async.d.ts +2 -0
- package/dist/core/util/Async.js +12 -1
- package/dist/core/util/Checks.js +1 -1
- package/dist/core/util/Slugs.js +4 -3
- package/dist/dashboard/App.css +13 -11
- package/dist/dashboard/App.d.ts +1 -0
- package/dist/dashboard/App.js +22 -6
- package/dist/dashboard/Href.d.ts +37 -0
- package/dist/dashboard/Href.js +46 -0
- package/dist/dashboard/app/ActivityStatus.js +34 -12
- package/dist/dashboard/app/AuthView.js +2 -2
- package/dist/dashboard/app/BlockSettings.d.ts +9 -0
- package/dist/dashboard/app/BlockSettings.js +16 -0
- package/dist/dashboard/app/BlockSheet.js +3 -3
- package/dist/dashboard/app/BlockSheetStoryFrame.js +2 -2
- package/dist/dashboard/app/CompactField.js +15 -15
- package/dist/dashboard/app/CroppedImage.js +2 -2
- package/dist/dashboard/app/DashboardLayout.d.ts +1 -1
- package/dist/dashboard/app/DashboardLayout.js +12 -0
- package/dist/dashboard/app/EntryActions.d.ts +11 -0
- package/dist/dashboard/app/EntryActions.js +30 -0
- package/dist/dashboard/app/EntryFields.d.ts +15 -2
- package/dist/dashboard/app/EntryFields.js +26 -5
- package/dist/dashboard/app/EntryHeader.js +17 -7
- package/dist/dashboard/app/EntryReferences.js +2 -2
- package/dist/dashboard/app/EntrySidebar.js +25 -23
- package/dist/dashboard/app/EntrySidebarPreview.js +2 -2
- package/dist/dashboard/app/EntryValidationModal.js +3 -3
- package/dist/dashboard/app/Explorer.js +84 -45
- package/dist/dashboard/app/ExplorerBatchActions.js +4 -4
- package/dist/dashboard/app/ExplorerControls.js +5 -5
- package/dist/dashboard/app/ExplorerList.js +2 -2
- package/dist/dashboard/app/ExplorerModal.js +3 -3
- package/dist/dashboard/app/ExplorerOverviewHeader.d.ts +6 -0
- package/dist/dashboard/app/ExplorerOverviewHeader.js +28 -0
- package/dist/dashboard/app/ExplorerTable.js +7 -7
- package/dist/dashboard/app/ExplorerTabs.d.ts +12 -0
- package/dist/dashboard/app/ExplorerTabs.js +42 -0
- package/dist/dashboard/app/ImageEditor.js +31 -31
- package/dist/dashboard/app/LinkPicker.js +3 -3
- package/dist/dashboard/app/LocaleMenu.js +9 -9
- package/dist/dashboard/app/MediaPreviewCell.js +5 -5
- package/dist/dashboard/app/OverviewCell.js +12 -10
- package/dist/dashboard/app/RemoveDialog.js +5 -0
- package/dist/dashboard/app/SearchBar.js +4 -4
- package/dist/dashboard/app/SettingsTab.d.ts +6 -0
- package/dist/dashboard/app/SettingsTab.js +27 -0
- package/dist/dashboard/app/SidebarTree.js +19 -19
- package/dist/dashboard/app/WorkspaceMenu.js +6 -6
- package/dist/dashboard/app/WorkspaceRoots.js +102 -98
- package/dist/dashboard/app/editor/FileEditor.js +18 -18
- package/dist/dashboard/app/pages/EntryPage.js +42 -39
- package/dist/dashboard/app/pages/RootPage.js +3 -3
- package/dist/dashboard/app/pages/SplashPage.js +94 -79
- package/dist/dashboard/app/pages/UsersPage.js +9 -9
- package/dist/dashboard/app/ui/DashboardModal.js +14 -14
- package/dist/dashboard/atoms/activity.d.ts +1 -0
- package/dist/dashboard/atoms/activity.js +8 -5
- package/dist/dashboard/atoms/config.d.ts +2 -0
- package/dist/dashboard/atoms/dashboard.d.ts +11 -0
- package/dist/dashboard/atoms/dashboard.js +8 -0
- package/dist/dashboard/atoms/editor.d.ts +7 -1
- package/dist/dashboard/atoms/editor.js +14 -2
- package/dist/dashboard/atoms/explorer.d.ts +15 -0
- package/dist/dashboard/atoms/explorer.js +43 -14
- package/dist/dashboard/atoms/graph.d.ts +5 -0
- package/dist/dashboard/atoms/graph.js +26 -1
- package/dist/dashboard/atoms/nav.d.ts +16 -10
- package/dist/dashboard/atoms/nav.js +96 -36
- package/dist/dashboard/atoms/overview.d.ts +26 -4
- package/dist/dashboard/atoms/overview.js +41 -12
- package/dist/dashboard/atoms/root.js +7 -1
- package/dist/dashboard/boot/ActivityEvent.d.ts +1 -1
- package/dist/dashboard/boot/Boot.js +6 -0
- package/dist/dashboard/boot/DashboardWorker.d.ts +6 -0
- package/dist/dashboard/boot/DashboardWorker.js +89 -8
- package/dist/dashboard/boot/LoadWorker.js +3 -0
- package/dist/dashboard/boot/SyncEvent.d.ts +7 -0
- package/dist/dashboard/boot/SyncEvent.js +14 -0
- package/dist/dashboard/boot/WorkerDB.d.ts +1 -0
- package/dist/dashboard/boot/WorkerDB.js +3 -0
- package/dist/dashboard/cms/EntryTable.d.ts +4 -3
- package/dist/dashboard/cms/EntryTable.js +2 -2
- package/dist/dashboard/cms/ViewProps.d.ts +3 -2
- package/dist/dashboard/cms/hooks.d.ts +34 -18
- package/dist/dashboard/cms/hooks.js +43 -18
- package/dist/dashboard/contexts.d.ts +3 -2
- package/dist/dashboard/contexts.js +3 -2
- package/dist/dashboard/hook/UseEntryEditor.d.ts +1 -1
- package/dist/dashboard/hook/UseEntryEditor.js +7 -3
- package/dist/dashboard/icons.d.ts +1 -0
- package/dist/dashboard/icons.js +15 -0
- package/dist/database/BrowserEntryStore.js +35 -13
- package/dist/database/DatabaseTables.js +1 -1
- package/dist/database/EntryDatabase.d.ts +10 -1
- package/dist/database/EntryDatabase.js +31 -10
- package/dist/database/EntryStore.js +1 -11
- package/dist/database/entry/EntryTable.d.ts +5 -3
- package/dist/database/entry/EntryTable.js +25 -8
- package/dist/database/query/Condition.d.ts +4 -1
- package/dist/database/query/Condition.js +16 -5
- package/dist/database/query/EntryQuery.js +59 -7
- package/dist/database/query/Relation.d.ts +13 -1
- package/dist/database/query/Relation.js +19 -1
- package/dist/database/query/Search.d.ts +7 -1
- package/dist/database/query/Search.js +54 -9
- package/dist/database/sync/EntrySyncer.d.ts +2 -0
- package/dist/database/sync/EntrySyncer.js +4 -2
- package/dist/database/sync/Ingest.d.ts +2 -1
- package/dist/database/sync/Ingest.js +20 -6
- package/dist/field/code/CodeField.view.js +12 -12
- package/dist/field/link/EntryLink.d.ts +4 -5
- package/dist/field/link/FileLink.d.ts +2 -2
- package/dist/field/link/ImageLink.d.ts +2 -5
- package/dist/field/link/ImageLink.js +0 -1
- package/dist/field/link/LinkField.view.js +18 -15
- package/dist/field/list/ListField.view.js +50 -35
- package/dist/field/metadata/MetadataField.view.js +14 -14
- package/dist/field/richtext/RichTextBlock.js +39 -35
- package/dist/field/richtext/RichTextExtensions.js +23 -23
- package/dist/field/richtext/RichTextField.view.js +6 -6
- package/dist/field/richtext/RichTextInsertMenu.js +5 -5
- package/dist/field/richtext/RichTextToolbar.js +26 -1
- package/dist/field/richtext/Toolbar.d.ts +9 -1
- package/dist/field/richtext/Toolbar.js +12 -2
- package/dist/field/select/SelectField.view.js +1 -0
- package/dist/index.css +174 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/next.d.ts +2 -0
- package/dist/next.edge.d.ts +2 -0
- package/dist/picker/entry/EntryPicker.d.ts +3 -1
- package/dist/picker/entry/EntryPicker.js +15 -1
- package/dist/preview/react.js +3 -0
- package/dist/query.d.ts +26 -14
- package/dist/query.js +4 -0
- package/dist/types.d.ts +8 -5
- package/dist/ui/Stack.d.ts +4 -2
- package/docs/ai-setup.md +3 -3
- package/docs/cli.md +4 -2
- package/docs/components/combo-box.md +105 -16
- package/docs/components/date-field.md +1 -1
- package/docs/components/dialog.md +51 -0
- package/docs/components/form.md +67 -0
- package/docs/components/link.md +10 -0
- package/docs/components/multiple-select.md +9 -17
- package/docs/components/number-field.md +5 -1
- package/docs/components/select.md +9 -20
- package/docs/components/stack.md +42 -0
- package/docs/components/table.md +83 -0
- package/docs/components/text-field.md +9 -1
- package/docs/components/time-field.md +5 -1
- package/docs/components.md +39 -1
- package/docs/configuration.md +3 -1
- package/docs/custom-fields.md +135 -7
- package/docs/deploy/self-host.md +2 -0
- package/docs/deploy.md +7 -4
- package/docs/fields/image.md +0 -1
- package/docs/fields/list.md +19 -0
- package/docs/fields/object.md +5 -3
- package/docs/fields/rich-text.md +31 -0
- package/docs/index.md +8 -6
- package/docs/instant-publishing.md +23 -2
- package/docs/overviews.md +128 -4
- package/docs/query/filtering.md +2 -0
- package/docs/query/related.md +58 -9
- package/docs/query.md +52 -1
- package/docs/quickstart.md +5 -5
- package/docs/schema/document.md +2 -0
- package/docs/schema/type.md +3 -1
- package/docs/upgrading.md +100 -19
- package/llms-full.txt +1022 -125
- package/package.json +1 -1
- package/readme.md +2 -2
- package/dist/chunks/{chunk-4ZA6KZHS.js → chunk-6FQ5IMNA.js} +3 -3
|
@@ -20,13 +20,14 @@ function useEditor() {
|
|
|
20
20
|
}
|
|
21
21
|
function useFieldInfo(field) {
|
|
22
22
|
const editor = useEditor();
|
|
23
|
+
if (typeof field === "string")
|
|
24
|
+
throw new Error(`Pass the field instead of its key: ${field}`);
|
|
23
25
|
const info = editor.get(field);
|
|
24
26
|
assert(info, "Field info not found in editor");
|
|
25
27
|
return info;
|
|
26
28
|
}
|
|
27
29
|
function useFieldNode(field) {
|
|
28
|
-
const { key } = useFieldInfo(field);
|
|
29
|
-
const editor = useEditor();
|
|
30
|
+
const { key, editor } = useFieldInfo(field);
|
|
30
31
|
const nodes = useAtomValueRaw(editor.node.nodes);
|
|
31
32
|
assert(nodes[key], `Node not found for field key: ${key}`);
|
|
32
33
|
return nodes[key];
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { EntryStatus } from 'alinea/core/Entry';
|
|
2
2
|
export interface EntryEditorVersionData {
|
|
3
3
|
path: string;
|
|
4
|
-
status: EntryStatus;
|
|
5
4
|
}
|
|
6
5
|
export interface EntryEditorVersion {
|
|
7
6
|
data: EntryEditorVersionData;
|
|
8
7
|
id: string;
|
|
9
8
|
locale: string | null;
|
|
10
9
|
path: string;
|
|
10
|
+
status: EntryStatus;
|
|
11
11
|
title: string;
|
|
12
12
|
type: string;
|
|
13
13
|
}
|
|
@@ -6,13 +6,15 @@ function useEntryEditor() {
|
|
|
6
6
|
const entry = useEntry();
|
|
7
7
|
if (!entry) return void 0;
|
|
8
8
|
const version = strictObject("entry editor activeVersion", {
|
|
9
|
+
// In 1.x data held the field values: only path is kept here, reading
|
|
10
|
+
// another field (a custom `status` too) throws instead of guessing
|
|
9
11
|
data: strictObject("entry editor activeVersion.data", {
|
|
10
|
-
path: entry.path
|
|
11
|
-
status: entry.status
|
|
12
|
+
path: entry.path
|
|
12
13
|
}),
|
|
13
14
|
id: entry.id,
|
|
14
15
|
locale: entry.locale,
|
|
15
16
|
path: entry.path,
|
|
17
|
+
status: entry.status,
|
|
16
18
|
title: entry.title,
|
|
17
19
|
type: entry.type
|
|
18
20
|
});
|
|
@@ -32,7 +34,9 @@ function strictObject(label, value) {
|
|
|
32
34
|
if (typeof property === "symbol")
|
|
33
35
|
return Reflect.get(target, property, receiver);
|
|
34
36
|
if (property in target) return Reflect.get(target, property, receiver);
|
|
35
|
-
throw new Error(
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${label}.${property} is not supported in dashboard v2, read the entry with useEntry() from alinea/cms`
|
|
39
|
+
);
|
|
36
40
|
}
|
|
37
41
|
});
|
|
38
42
|
}
|
|
@@ -181,3 +181,4 @@ export declare function IcRoundAudioFile(props: SVGProps<SVGSVGElement>): import
|
|
|
181
181
|
export declare function IcRoundRotateRight(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
182
182
|
export declare function IcRoundCrop(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
183
183
|
export declare function IcRoundDriveFileMove(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
184
|
+
export declare function IcRoundDownload(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
package/dist/dashboard/icons.js
CHANGED
|
@@ -2640,6 +2640,20 @@ function IcRoundDriveFileMove(props) {
|
|
|
2640
2640
|
}
|
|
2641
2641
|
) });
|
|
2642
2642
|
}
|
|
2643
|
+
function IcRoundDownload(props) {
|
|
2644
|
+
return /* @__PURE__ */ jsx(
|
|
2645
|
+
"svg",
|
|
2646
|
+
{
|
|
2647
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2648
|
+
width: "1em",
|
|
2649
|
+
height: "1em",
|
|
2650
|
+
viewBox: "0 0 24 24",
|
|
2651
|
+
fill: "currentColor",
|
|
2652
|
+
...props,
|
|
2653
|
+
children: /* @__PURE__ */ jsx("path", { d: "M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71M5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1" })
|
|
2654
|
+
}
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2643
2657
|
export {
|
|
2644
2658
|
IcAlignCenter,
|
|
2645
2659
|
IcAlignJustify,
|
|
@@ -2704,6 +2718,7 @@ export {
|
|
|
2704
2718
|
IcRoundDelete,
|
|
2705
2719
|
IcRoundDescription,
|
|
2706
2720
|
IcRoundDesktopWindows,
|
|
2721
|
+
IcRoundDownload,
|
|
2707
2722
|
IcRoundDragHandle,
|
|
2708
2723
|
IcRoundDragIndicator,
|
|
2709
2724
|
IcRoundDriveFileMove,
|
|
@@ -16,19 +16,32 @@ import { DatabaseSource } from "./DatabaseSource.js";
|
|
|
16
16
|
import { wasmHandle, wasmSqlite } from "./driver/WasmDatabase.js";
|
|
17
17
|
var cacheSize = -65536;
|
|
18
18
|
var keepBranches = 2;
|
|
19
|
+
var rewriteLimit = 1;
|
|
19
20
|
var BrowserEntryStore = class _BrowserEntryStore extends EntryStore {
|
|
20
21
|
#session;
|
|
21
22
|
#storage;
|
|
22
23
|
#stored;
|
|
23
24
|
#written = Promise.resolve();
|
|
24
25
|
#cleanup = Promise.resolve();
|
|
26
|
+
/**
|
|
27
|
+
* The entries were derived again from a base of another config. Refetching
|
|
28
|
+
* everything after each deploy that changes the config costs more than
|
|
29
|
+
* the layout that deriving leaves, so its first save is not counted.
|
|
30
|
+
*/
|
|
31
|
+
#derived = false;
|
|
25
32
|
static async open(config, options) {
|
|
26
33
|
const { name, fingerprint } = options;
|
|
27
34
|
const storage = options.storage ?? indexedDBSnapshots(`${versionedCacheName(name)}-snapshots`);
|
|
28
35
|
const cleanup = !options.storage && globalThis.indexedDB ? cleanupOldCaches(indexedDB, name).catch(() => {
|
|
29
36
|
}) : Promise.resolve();
|
|
30
37
|
const Database = await wasmSqlite();
|
|
31
|
-
const
|
|
38
|
+
const empty = () => storage.session(new Database(), { branch: fingerprint });
|
|
39
|
+
let session = options.reset ? empty() : await openBase(storage, Database, fingerprint);
|
|
40
|
+
const base = session.snapshot;
|
|
41
|
+
if (base && rewritten(base) > base.size * rewriteLimit) {
|
|
42
|
+
await session.close();
|
|
43
|
+
session = empty();
|
|
44
|
+
}
|
|
32
45
|
const store = await _BrowserEntryStore.#on(
|
|
33
46
|
config,
|
|
34
47
|
session,
|
|
@@ -36,12 +49,7 @@ var BrowserEntryStore = class _BrowserEntryStore extends EntryStore {
|
|
|
36
49
|
fingerprint
|
|
37
50
|
).catch((error) => {
|
|
38
51
|
if (!session.snapshot) throw error;
|
|
39
|
-
return _BrowserEntryStore.#on(
|
|
40
|
-
config,
|
|
41
|
-
storage.session(new Database(), { branch: fingerprint }),
|
|
42
|
-
storage,
|
|
43
|
-
fingerprint
|
|
44
|
-
);
|
|
52
|
+
return _BrowserEntryStore.#on(config, empty(), storage, fingerprint);
|
|
45
53
|
});
|
|
46
54
|
store.#cleanup = cleanup;
|
|
47
55
|
return store;
|
|
@@ -60,9 +68,11 @@ var BrowserEntryStore = class _BrowserEntryStore extends EntryStore {
|
|
|
60
68
|
await db.run(sql`pragma page_size = 16384`);
|
|
61
69
|
await EntryDatabase.createSchema(db, config, ReadonlyTree.EMPTY.sha);
|
|
62
70
|
const database = new EntryDatabase(config, db, { fork: preview });
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
const derived = branch !== void 0 && branch !== fingerprint;
|
|
72
|
+
if (derived) await withoutJournal(sqlite, () => database.reindex(config));
|
|
73
|
+
const store = new _BrowserEntryStore(config, database, session, storage);
|
|
74
|
+
store.#derived = derived;
|
|
75
|
+
return store;
|
|
66
76
|
} catch (error) {
|
|
67
77
|
await db.close();
|
|
68
78
|
throw error;
|
|
@@ -89,9 +99,17 @@ var BrowserEntryStore = class _BrowserEntryStore extends EntryStore {
|
|
|
89
99
|
const session = this.#session;
|
|
90
100
|
const storage = this.#storage;
|
|
91
101
|
const key = `${revision}-${session.branch}`;
|
|
92
|
-
const save = (key2) => this.database.whileIdle(async () =>
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
const save = (key2) => this.database.whileIdle(async () => {
|
|
103
|
+
if (!session.held) return void 0;
|
|
104
|
+
const base = session.snapshot;
|
|
105
|
+
const added = this.#derived ? 0 : session.held;
|
|
106
|
+
this.#derived = false;
|
|
107
|
+
const meta = {
|
|
108
|
+
tree: revision,
|
|
109
|
+
rewritten: base ? rewritten(base) + added : 0
|
|
110
|
+
};
|
|
111
|
+
return { saved: session.save({ key: key2, meta }) };
|
|
112
|
+
}).then((result) => result?.saved);
|
|
95
113
|
const written = save(key).then(
|
|
96
114
|
(result) => result?.status === "mismatch" ? save(`${key}-${crypto.randomUUID()}`) : result
|
|
97
115
|
).then(() => storage.retain({ branches: keepBranches })).catch(() => {
|
|
@@ -106,6 +124,10 @@ var BrowserEntryStore = class _BrowserEntryStore extends EntryStore {
|
|
|
106
124
|
await super.close();
|
|
107
125
|
}
|
|
108
126
|
};
|
|
127
|
+
function rewritten(base) {
|
|
128
|
+
const value = base.meta.rewritten;
|
|
129
|
+
return typeof value === "number" ? value : 0;
|
|
130
|
+
}
|
|
109
131
|
var corruptCodes = /* @__PURE__ */ new Set(["SQLITE_CORRUPT", "SQLITE_NOTADB"]);
|
|
110
132
|
var corruptMessage = /database disk image is malformed|file is not a database/;
|
|
111
133
|
function isCorruptDatabaseError(error) {
|
|
@@ -93,5 +93,14 @@ export declare class EntryDatabase extends Graph implements AsyncDisposable {
|
|
|
93
93
|
getSourceFileStats(): Promise<Map<string, FileStat>>;
|
|
94
94
|
/** Replace the recorded file stats after syncing from a filesystem source. */
|
|
95
95
|
setSourceFileStats(stats: ReadonlyMap<string, FileStat>): Promise<void>;
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Optimize the database and rewrite it when it has pages to reclaim, or
|
|
98
|
+
* always with `rewrite`. A file that synced many changes keeps the pages of
|
|
99
|
+
* its tables scattered over it, without any to reclaim: readers that read
|
|
100
|
+
* ahead, such as the dashboard reading a stored copy, then read it page by
|
|
101
|
+
* page. Rewriting lays every table out in order.
|
|
102
|
+
*/
|
|
103
|
+
compact(options?: {
|
|
104
|
+
rewrite?: boolean;
|
|
105
|
+
}): Promise<void>;
|
|
97
106
|
}
|
|
@@ -67,6 +67,11 @@ var EntryDatabase = class _EntryDatabase extends Graph {
|
|
|
67
67
|
#tree;
|
|
68
68
|
#initialTree;
|
|
69
69
|
#recordsTree;
|
|
70
|
+
/**
|
|
71
|
+
* Overlays answer one preview payload and are thrown away; they keep the
|
|
72
|
+
* statistics of the database they fork.
|
|
73
|
+
*/
|
|
74
|
+
#analyzes = true;
|
|
70
75
|
/** The connection is already inside a transaction: never open nested ones. */
|
|
71
76
|
#transactional = false;
|
|
72
77
|
#changeListeners = /* @__PURE__ */ new Set();
|
|
@@ -124,6 +129,7 @@ var EntryDatabase = class _EntryDatabase extends Graph {
|
|
|
124
129
|
recordsTree: false
|
|
125
130
|
});
|
|
126
131
|
overlay.#tree = tree;
|
|
132
|
+
overlay.#analyzes = false;
|
|
127
133
|
try {
|
|
128
134
|
await overlay.syncWith(source);
|
|
129
135
|
return overlay;
|
|
@@ -172,7 +178,7 @@ var EntryDatabase = class _EntryDatabase extends Graph {
|
|
|
172
178
|
tree: revision === ReadonlyTree.EMPTY.sha ? ReadonlyTree.EMPTY : null
|
|
173
179
|
});
|
|
174
180
|
await syncFieldIndexes(db, config);
|
|
175
|
-
await analyze(db);
|
|
181
|
+
if (!await hasStatistics(db)) await analyze(db);
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
178
184
|
* Synchronize a source through this database's single prepared syncer,
|
|
@@ -193,7 +199,8 @@ var EntryDatabase = class _EntryDatabase extends Graph {
|
|
|
193
199
|
previousTree: this.#tree?.sha === current ? this.#tree : void 0,
|
|
194
200
|
withinTransaction: this.#transactional,
|
|
195
201
|
validate: options?.validate ?? true,
|
|
196
|
-
recordsTree: this.#recordsTree
|
|
202
|
+
recordsTree: this.#recordsTree,
|
|
203
|
+
onProgress: options?.onProgress
|
|
197
204
|
});
|
|
198
205
|
this.#tree = tree;
|
|
199
206
|
if (changed.length > 0) await this.#optimize();
|
|
@@ -392,7 +399,7 @@ var EntryDatabase = class _EntryDatabase extends Graph {
|
|
|
392
399
|
return this.close();
|
|
393
400
|
}
|
|
394
401
|
async #optimize() {
|
|
395
|
-
if (!this.#transactional) await analyze(this.#db);
|
|
402
|
+
if (this.#analyzes && !this.#transactional) await analyze(this.#db);
|
|
396
403
|
}
|
|
397
404
|
getRevision() {
|
|
398
405
|
return this.#queue.run(() => this.#getRevision(this.#db));
|
|
@@ -525,28 +532,42 @@ var EntryDatabase = class _EntryDatabase extends Graph {
|
|
|
525
532
|
)
|
|
526
533
|
);
|
|
527
534
|
}
|
|
528
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Optimize the database and rewrite it when it has pages to reclaim, or
|
|
537
|
+
* always with `rewrite`. A file that synced many changes keeps the pages of
|
|
538
|
+
* its tables scattered over it, without any to reclaim: readers that read
|
|
539
|
+
* ahead, such as the dashboard reading a stored copy, then read it page by
|
|
540
|
+
* page. Rewriting lays every table out in order.
|
|
541
|
+
*/
|
|
542
|
+
async compact(options = {}) {
|
|
529
543
|
await this.#queue.run(async () => {
|
|
530
544
|
await this.#db.run(sql`pragma optimize`);
|
|
531
545
|
const free = await this.#db.get(
|
|
532
546
|
sql`pragma freelist_count`
|
|
533
547
|
);
|
|
534
|
-
if (free?.freelist_count)
|
|
548
|
+
if (options.rewrite || free?.freelist_count)
|
|
549
|
+
await this.#db.run(sql`vacuum`);
|
|
535
550
|
});
|
|
536
551
|
}
|
|
537
552
|
};
|
|
538
553
|
async function analyze(db) {
|
|
539
554
|
try {
|
|
540
555
|
await db.run(sql`pragma analysis_limit = 400`);
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
sql`select 1 from sqlite_master where name = 'sqlite_stat1'`
|
|
556
|
+
await db.run(
|
|
557
|
+
await hasStatistics(db) ? sql`pragma optimize = 0x10002` : sql`analyze`
|
|
544
558
|
);
|
|
545
|
-
const analyzed = stats && await db.get(sql`select 1 from sqlite_stat1 where tbl = ${entries}`);
|
|
546
|
-
await db.run(analyzed ? sql`pragma optimize = 0x10002` : sql`analyze`);
|
|
547
559
|
} catch {
|
|
548
560
|
}
|
|
549
561
|
}
|
|
562
|
+
async function hasStatistics(db) {
|
|
563
|
+
const entries = getTable(EntryIndexTable).name;
|
|
564
|
+
const stats = await db.get(
|
|
565
|
+
sql`select 1 from sqlite_master where name = 'sqlite_stat1'`
|
|
566
|
+
);
|
|
567
|
+
return Boolean(
|
|
568
|
+
stats && await db.get(sql`select 1 from sqlite_stat1 where tbl = ${entries}`)
|
|
569
|
+
);
|
|
570
|
+
}
|
|
550
571
|
export {
|
|
551
572
|
EntryDatabase
|
|
552
573
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../chunks/chunk-CQ52GEHY.js";
|
|
2
2
|
|
|
3
3
|
// src/database/EntryStore.ts
|
|
4
|
-
import { applyChangesFrom } from "alinea/core/source/Source";
|
|
4
|
+
import { applyChangesFrom, sourceAtTree } from "alinea/core/source/Source";
|
|
5
5
|
import { OverlaySource } from "alinea/core/source/OverlaySource";
|
|
6
6
|
import { ReadonlyTree } from "alinea/core/source/Tree";
|
|
7
7
|
import { createRecord } from "alinea/core/EntryRecord";
|
|
@@ -215,16 +215,6 @@ async function changesSince(source, from) {
|
|
|
215
215
|
await changes.applyChangesFrom(source, from.diff(tree), tree);
|
|
216
216
|
return changes;
|
|
217
217
|
}
|
|
218
|
-
function sourceAtTree(source, tree) {
|
|
219
|
-
return {
|
|
220
|
-
async getTreeIfDifferent(revision) {
|
|
221
|
-
return revision === tree.sha ? void 0 : tree;
|
|
222
|
-
},
|
|
223
|
-
getBlobs(shas, options) {
|
|
224
|
-
return source.getBlobs(shas, options);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
218
|
export {
|
|
229
219
|
EntryStore
|
|
230
220
|
};
|
|
@@ -139,9 +139,11 @@ export declare function isOrderedField(field: unknown): boolean;
|
|
|
139
139
|
/** Metadata fields whose queries order by their stored value. */
|
|
140
140
|
export declare const orderedMetadata: Set<string>;
|
|
141
141
|
/**
|
|
142
|
-
* Index the stored value of every ordered field in the config,
|
|
143
|
-
*
|
|
144
|
-
*
|
|
142
|
+
* Index the stored value of every ordered field in the config, and of the
|
|
143
|
+
* ordered metadata. A field is indexed per type that has it, over the rows of
|
|
144
|
+
* that type only: queries of one type find the index, and the rows of other
|
|
145
|
+
* types don't take up space in it. The metadata is indexed over every row.
|
|
146
|
+
* Index names are `type:field` and `metadata.name`, which can't collide.
|
|
145
147
|
*/
|
|
146
148
|
export declare function syncFieldIndexes(db: Database, config: Config): Promise<void>;
|
|
147
149
|
/** An Entry plus the physical-version and local-index fields. */
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
json,
|
|
5
5
|
text,
|
|
6
6
|
varchar
|
|
7
|
-
} from "../../chunks/chunk-
|
|
7
|
+
} from "../../chunks/chunk-2PP7WJUJ.js";
|
|
8
8
|
import {
|
|
9
9
|
primaryKey,
|
|
10
10
|
table
|
|
@@ -118,11 +118,13 @@ function isOrderedField(field) {
|
|
|
118
118
|
var fieldIndexPrefix = "alinea_entry_index_by_field_";
|
|
119
119
|
var orderedMetadata = /* @__PURE__ */ new Set(["createdAt", "updatedAt"]);
|
|
120
120
|
async function syncFieldIndexes(db, config) {
|
|
121
|
-
const wanted = /* @__PURE__ */ new
|
|
122
|
-
for (const name of orderedMetadata)
|
|
123
|
-
|
|
121
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const name of orderedMetadata)
|
|
123
|
+
wanted.set(`metadata.${name}`, { path: ["metadata", name] });
|
|
124
|
+
for (const [typeName, type] of Object.entries(config.schema))
|
|
124
125
|
for (const [name, field] of Object.entries(Type.fields(type)))
|
|
125
|
-
if (isOrderedField(field))
|
|
126
|
+
if (isOrderedField(field))
|
|
127
|
+
wanted.set(`${typeName}:${name}`, { type: typeName, path: [name] });
|
|
126
128
|
const rows = await db.all(
|
|
127
129
|
sql`select name from sqlite_master where type = 'index'`
|
|
128
130
|
);
|
|
@@ -133,13 +135,28 @@ async function syncFieldIndexes(db, config) {
|
|
|
133
135
|
for (const name of existing)
|
|
134
136
|
if (!wanted.has(name))
|
|
135
137
|
await db.run(sql`drop index ${sql.identifier(fieldIndexPrefix + name)}`);
|
|
136
|
-
for (const name of wanted) {
|
|
138
|
+
for (const [name, { type, path }] of wanted) {
|
|
137
139
|
if (existing.has(name)) continue;
|
|
138
|
-
const value = jsonField(sql.identifier("data"),
|
|
140
|
+
const value = jsonField(sql.identifier("data"), path);
|
|
141
|
+
const where = type ? sql`where ${sql.identifier("type")} = ${sql.inline(type)}` : sql``;
|
|
139
142
|
await db.run(sql`create index ${sql.identifier(fieldIndexPrefix + name)}
|
|
140
143
|
on ${EntryIndexTable}(${sql.identifier("type")}, ${value},
|
|
141
|
-
${sql.identifier("index")}, ${sql.identifier("filePath")})`);
|
|
144
|
+
${sql.identifier("index")}, ${sql.identifier("filePath")}) ${where}`);
|
|
142
145
|
}
|
|
146
|
+
await createRecentIndexes(db);
|
|
147
|
+
}
|
|
148
|
+
async function createRecentIndexes(db) {
|
|
149
|
+
const workspace = sql.identifier("workspace");
|
|
150
|
+
const index2 = sql.identifier("index");
|
|
151
|
+
const filePath = sql.identifier("filePath");
|
|
152
|
+
const updatedAt = jsonField(sql.identifier("data"), ["metadata", "updatedAt"]);
|
|
153
|
+
await db.run(sql`create index if not exists
|
|
154
|
+
${sql.identifier("alinea_entry_index_recent_updated")}
|
|
155
|
+
on ${EntryIndexTable}(${workspace}, ${updatedAt}, ${index2}, ${filePath})`);
|
|
156
|
+
await db.run(sql`create index if not exists
|
|
157
|
+
${sql.identifier("alinea_entry_index_recent_id")}
|
|
158
|
+
on ${EntryIndexTable}(${workspace}, ${sql.identifier("id")} desc,
|
|
159
|
+
${index2}, ${filePath})`);
|
|
143
160
|
}
|
|
144
161
|
function entryIndexRow(entry) {
|
|
145
162
|
const payload = entry.payload ?? JSON.stringify(createRecord(entry, entry.versionStatus), null, 2);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type RelationCondition } from 'alinea/core/Filter';
|
|
1
2
|
import { type HasSql, type Sql } from 'rado';
|
|
2
3
|
type FieldSql = (name: string) => HasSql;
|
|
4
|
+
/** Whether a query of entries related to a field finds an entry */
|
|
5
|
+
type RelationSql = (name: string, query: RelationCondition) => Sql<boolean>;
|
|
3
6
|
export declare function jsonField(target: HasSql, segments: Array<string>): HasSql<unknown>;
|
|
4
7
|
/**
|
|
5
8
|
* Match a column against a JSON array parameter, so one prepared statement
|
|
@@ -8,6 +11,6 @@ export declare function jsonField(target: HasSql, segments: Array<string>): HasS
|
|
|
8
11
|
export declare function inJson(column: HasSql, values: HasSql<string>): Sql<boolean>;
|
|
9
12
|
/** Compile a condition; `depth` names the array items it iterates. */
|
|
10
13
|
export declare function compileCondition(field: HasSql, condition: unknown, depth?: number): Sql<boolean>;
|
|
11
|
-
export declare function compileFilter(filter: unknown, field: FieldSql): Sql<boolean>;
|
|
14
|
+
export declare function compileFilter(filter: unknown, field: FieldSql, relation?: RelationSql): Sql<boolean>;
|
|
12
15
|
export declare function arrayIncludes(field: HasSql, predicate: (item: HasSql) => Sql<boolean>, depth?: number): Sql<boolean>;
|
|
13
16
|
export {};
|
|
@@ -49,20 +49,29 @@ function equals(field, value) {
|
|
|
49
49
|
function compileCondition(field, condition, depth = 0) {
|
|
50
50
|
return conditionSql(field, parseCondition(condition), depth);
|
|
51
51
|
}
|
|
52
|
-
function compileFilter(filter, field) {
|
|
53
|
-
return filterSql(parseFilter(filter), field, 0);
|
|
52
|
+
function compileFilter(filter, field, relation) {
|
|
53
|
+
return filterSql(parseFilter(filter), field, 0, relation);
|
|
54
54
|
}
|
|
55
|
-
function filterSql(node, field, depth) {
|
|
55
|
+
function filterSql(node, field, depth, relation) {
|
|
56
56
|
switch (node.op) {
|
|
57
57
|
case "and": {
|
|
58
|
-
const clauses = node.nodes.map(
|
|
58
|
+
const clauses = node.nodes.map(
|
|
59
|
+
(node2) => filterSql(node2, field, depth, relation)
|
|
60
|
+
);
|
|
59
61
|
return clauses.length ? and(...clauses) : sql.value(true);
|
|
60
62
|
}
|
|
61
63
|
case "or": {
|
|
62
|
-
const clauses = node.nodes.map(
|
|
64
|
+
const clauses = node.nodes.map(
|
|
65
|
+
(node2) => filterSql(node2, field, depth, relation)
|
|
66
|
+
);
|
|
63
67
|
return clauses.length ? or(...clauses) : sql.value(false);
|
|
64
68
|
}
|
|
65
69
|
case "field":
|
|
70
|
+
if (node.condition.op === "relation") {
|
|
71
|
+
if (!relation)
|
|
72
|
+
throw new Error("Relation conditions are not supported here");
|
|
73
|
+
return relation(node.name, node.condition.query);
|
|
74
|
+
}
|
|
66
75
|
return conditionSql(field(node.name), node.condition, depth);
|
|
67
76
|
}
|
|
68
77
|
}
|
|
@@ -107,6 +116,8 @@ function conditionSql(field, node, depth) {
|
|
|
107
116
|
(item) => conditionSql(item, node.item, depth + 1),
|
|
108
117
|
depth
|
|
109
118
|
);
|
|
119
|
+
case "relation":
|
|
120
|
+
throw new Error("Relation conditions only apply to entries");
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
function arrayIncludes(field, predicate, depth = 0) {
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
} from "alinea/core/Internal";
|
|
45
45
|
import { getScope } from "alinea/core/Scope";
|
|
46
46
|
import { isRecord } from "alinea/core/util/Objects";
|
|
47
|
+
import { LinksField } from "alinea/field/link/LinkField";
|
|
47
48
|
import {
|
|
48
49
|
EntryIndexTable,
|
|
49
50
|
EntryReferenceTable,
|
|
@@ -58,7 +59,11 @@ import {
|
|
|
58
59
|
jsonField
|
|
59
60
|
} from "./Condition.js";
|
|
60
61
|
import { searchableText, searchQuery } from "./Search.js";
|
|
61
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
linkRelation,
|
|
64
|
+
referencedByCondition,
|
|
65
|
+
relationCondition
|
|
66
|
+
} from "./Relation.js";
|
|
62
67
|
var builder = new Builder();
|
|
63
68
|
function readJson(value, specs) {
|
|
64
69
|
if (value === null) return void 0;
|
|
@@ -131,10 +136,10 @@ var Expressions = class {
|
|
|
131
136
|
case "entryField":
|
|
132
137
|
return selecting ? this.selection(internal.name, internal.path) : this.index(internal.name, internal.path);
|
|
133
138
|
case "field": {
|
|
134
|
-
const
|
|
135
|
-
if (!
|
|
139
|
+
const path = this.#scope.dataPathOf(expression);
|
|
140
|
+
if (!path)
|
|
136
141
|
throw new Error("Field expression is not in the configured schema");
|
|
137
|
-
return this.data(
|
|
142
|
+
return this.data(path, selecting);
|
|
138
143
|
}
|
|
139
144
|
case "value":
|
|
140
145
|
return sql.value(internal.value);
|
|
@@ -218,8 +223,24 @@ function compileEntryQuery(config, query, options = {}) {
|
|
|
218
223
|
throw new Error("SQL preview requires its dedicated query stage");
|
|
219
224
|
const scope = getScope(config);
|
|
220
225
|
function scalar(relationQuery) {
|
|
226
|
+
if (relationQuery.count) {
|
|
227
|
+
const { rows: rows2 } = compileEntryQuery(
|
|
228
|
+
config,
|
|
229
|
+
{ ...relationQuery, status: query.status ?? "published" },
|
|
230
|
+
{
|
|
231
|
+
source: entry,
|
|
232
|
+
entry: alias(baseEntry, `alinea_scalar_${depth + 1}`),
|
|
233
|
+
depth: depth + 1,
|
|
234
|
+
baseEntry
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
const matches = rows2.as(`alinea_scalar_count_${depth + 1}`);
|
|
238
|
+
return sql`(${getQuery(builder.select(count()).from(matches))})`;
|
|
239
|
+
}
|
|
221
240
|
if (!isRecord(relationQuery.select) || !hasExpr(relationQuery.select))
|
|
222
|
-
throw new Error(
|
|
241
|
+
throw new Error(
|
|
242
|
+
"A relation expression must select a single expression or count"
|
|
243
|
+
);
|
|
223
244
|
const { rows } = compileEntryQuery(
|
|
224
245
|
config,
|
|
225
246
|
{ ...relationQuery, status: query.status ?? "published" },
|
|
@@ -247,6 +268,14 @@ function compileEntryQuery(config, query, options = {}) {
|
|
|
247
268
|
const name = scope.nameOf(edge.field);
|
|
248
269
|
if (!name) throw new Error("Link field is not in the configured schema");
|
|
249
270
|
links = linkRelation(source, name, edge.edge === "entryMultiple");
|
|
271
|
+
} else if (edge.edge === "referencedBy") {
|
|
272
|
+
const field = edge.field;
|
|
273
|
+
const path = field && scope.dataPathOf(field);
|
|
274
|
+
if (field && !path)
|
|
275
|
+
throw new Error("Link field is not in the configured schema");
|
|
276
|
+
const via = path && { path, multiple: field instanceof LinksField };
|
|
277
|
+
const ownLocale = query.locale === void 0 && !query.preferredLocale;
|
|
278
|
+
conditions.push(referencedByCondition(entry, source, via, ownLocale));
|
|
250
279
|
} else conditions.push(relationCondition(entry, edge, source));
|
|
251
280
|
}
|
|
252
281
|
conditions.push(statusCondition(entry, query.status));
|
|
@@ -290,7 +319,9 @@ function compileEntryQuery(config, query, options = {}) {
|
|
|
290
319
|
if (!name) throw new Error("Query type is not in the configured schema");
|
|
291
320
|
return name;
|
|
292
321
|
});
|
|
293
|
-
conditions.push(
|
|
322
|
+
conditions.push(
|
|
323
|
+
names.length === 1 ? eq(entry.type, names[0]) : compileCondition(membership.index("type"), { in: names })
|
|
324
|
+
);
|
|
294
325
|
}
|
|
295
326
|
const location = Array.isArray(query.location) ? query.location : query.location && scope.locationOf(query.location);
|
|
296
327
|
if (location && location.length >= 1 && location.length <= 3) {
|
|
@@ -307,8 +338,29 @@ function compileEntryQuery(config, query, options = {}) {
|
|
|
307
338
|
(item) => compileCondition(jsonField(item, ["url"]), query.alias, 1)
|
|
308
339
|
)
|
|
309
340
|
);
|
|
341
|
+
function related(name, condition) {
|
|
342
|
+
if (name !== "_referenced" || condition.edge !== "referencedBy")
|
|
343
|
+
throw new Error(`Unsupported relation condition on ${name}`);
|
|
344
|
+
const { rows } = compileEntryQuery(
|
|
345
|
+
config,
|
|
346
|
+
{
|
|
347
|
+
...condition,
|
|
348
|
+
count: true,
|
|
349
|
+
status: query.status ?? "published"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
source: entry,
|
|
353
|
+
entry: alias(baseEntry, `alinea_exists_${depth + 1}`),
|
|
354
|
+
depth: depth + 1,
|
|
355
|
+
baseEntry
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
return exists(rows);
|
|
359
|
+
}
|
|
310
360
|
if (query.filter !== void 0)
|
|
311
|
-
conditions.push(
|
|
361
|
+
conditions.push(
|
|
362
|
+
compileFilter(query.filter, (name) => membership.field(name), related)
|
|
363
|
+
);
|
|
312
364
|
if (Array.isArray(query.groupBy))
|
|
313
365
|
throw new Error("groupBy must be a single field");
|
|
314
366
|
const grouping = query.groupBy ? [membership.expr(query.groupBy)] : void 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { EdgeQuery } from 'alinea/core/Graph';
|
|
2
2
|
import { type Sql } from 'rado';
|
|
3
|
-
import type
|
|
3
|
+
import { type EntryIndexTarget } from '../entry/EntryTable.js';
|
|
4
4
|
/** Expand the stored references as SQL rows, retaining list order and duplicates. */
|
|
5
5
|
export declare function linkRelation(source: EntryIndexTarget, field: string, multiple: boolean): {
|
|
6
6
|
target: Sql<unknown>;
|
|
@@ -9,3 +9,15 @@ export declare function linkRelation(source: EntryIndexTarget, field: string, mu
|
|
|
9
9
|
};
|
|
10
10
|
/** Restrict related identities in SQL before the query's own filters/paging. */
|
|
11
11
|
export declare function relationCondition(entry: EntryIndexTarget, query: EdgeQuery, source: EntryIndexTarget): Sql<boolean>;
|
|
12
|
+
/** A link field the referencing entries link through */
|
|
13
|
+
export interface ReferenceField {
|
|
14
|
+
/** Where the field is stored in the entry data */
|
|
15
|
+
path: Array<string>;
|
|
16
|
+
multiple: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Entries that link to the source: the reference index finds them, a link
|
|
20
|
+
* field narrows them down to the ones linking through it. They share the
|
|
21
|
+
* locale of the source, unless either has none.
|
|
22
|
+
*/
|
|
23
|
+
export declare function referencedByCondition(entry: EntryIndexTarget, source: EntryIndexTarget, field?: ReferenceField, sameLocale?: boolean): Sql<boolean>;
|
|
@@ -29,7 +29,10 @@ import {
|
|
|
29
29
|
import "../../chunks/chunk-CQ52GEHY.js";
|
|
30
30
|
|
|
31
31
|
// src/database/query/Relation.ts
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
EntryReferenceTable
|
|
34
|
+
} from "../entry/EntryTable.js";
|
|
35
|
+
import { arrayIncludes, jsonField } from "./Condition.js";
|
|
33
36
|
function linkRelation(source, field, multiple) {
|
|
34
37
|
const value = getSql(jsonField(source.data, [field])).forSelection();
|
|
35
38
|
const array = multiple ? value : sql`json_array(json(${value}))`;
|
|
@@ -117,7 +120,22 @@ function relationCondition(entry, query, source) {
|
|
|
117
120
|
throw new Error(`SQL link relation is not implemented: ${query.edge}`);
|
|
118
121
|
}
|
|
119
122
|
}
|
|
123
|
+
function referencedByCondition(entry, source, field, sameLocale = true) {
|
|
124
|
+
const references = new Builder().select(EntryReferenceTable.source).from(EntryReferenceTable).where(eq(EntryReferenceTable.targetId, source.id));
|
|
125
|
+
const value = field && jsonField(entry.data, field.path);
|
|
126
|
+
const linked = !value ? sql.value(true) : field.multiple ? arrayIncludes(value, (item) => eq(jsonField(item, ["_entry"]), source.id)) : eq(jsonField(value, ["_entry"]), source.id);
|
|
127
|
+
return and(
|
|
128
|
+
inArray(entry.rowid, references),
|
|
129
|
+
linked,
|
|
130
|
+
sameLocale ? or(
|
|
131
|
+
sql`${source.locale} is null`,
|
|
132
|
+
sql`${entry.locale} is null`,
|
|
133
|
+
eq(entry.locale, source.locale)
|
|
134
|
+
) : sql.value(true)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
120
137
|
export {
|
|
121
138
|
linkRelation,
|
|
139
|
+
referencedByCondition,
|
|
122
140
|
relationCondition
|
|
123
141
|
};
|