alinea 2.0.0-preview.12 → 2.0.0-preview.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +16 -8
- package/dist/adapter/next/dashboardUrl.d.ts +6 -0
- package/dist/adapter/next/dashboardUrl.js +13 -0
- package/dist/adapter/next/syncCheck.d.ts +15 -2
- package/dist/adapter/next/syncCheck.js +26 -9
- package/dist/backend/Handler.js +5 -1
- package/dist/backend/api/BlobStore.d.ts +21 -0
- package/dist/backend/api/BlobStore.js +63 -0
- package/dist/backend/api/CreateBackend.js +10 -8
- package/dist/backend/api/DatabaseApi.d.ts +4 -1
- package/dist/backend/api/DatabaseApi.js +88 -4
- package/dist/backend/api/GithubApi.js +47 -24
- package/dist/backend/api/database.js +6 -6
- package/dist/bundled.d.ts +676 -280
- package/dist/chunks/{bun-sqlite-5SHXN3AT.js → bun-sqlite-RI6BYU5S.js} +3 -3
- package/dist/chunks/{chunk-OHGAVV4G.js → chunk-3QFSGTGF.js} +2 -3
- package/dist/chunks/{chunk-QJGGYF6E.js → chunk-4ZA6KZHS.js} +5 -5
- package/dist/chunks/{chunk-ASYKC6IE.js → chunk-5H4D5JQL.js} +1 -1
- package/dist/chunks/{chunk-ETWOY4AL.js → chunk-A2I6MCCS.js} +1 -1
- package/dist/chunks/{chunk-W5AXN7GH.js → chunk-CYVOGRSD.js} +1 -2
- package/dist/chunks/{chunk-TIJTT5ZI.js → chunk-G43W7AME.js} +1 -0
- package/dist/chunks/{chunk-RE523BQ7.js → chunk-HOETF6HN.js} +5829 -5830
- package/dist/chunks/{chunk-AIU6CMVT.js → chunk-PTYZMV5G.js} +1 -1
- package/dist/chunks/{chunk-JOF663EG.js → chunk-QHHMKJUO.js} +1 -1
- package/dist/chunks/{chunk-66YZ47TY.js → chunk-RAR44T5O.js} +1 -1
- package/dist/chunks/{chunk-QSN6RH6X.js → chunk-VA6NC3MI.js} +1 -1
- package/dist/chunks/{node-sqlite-XVNTNMCY.js → node-sqlite-C7VJ2NSN.js} +3 -3
- package/dist/cli/Mcp.js +1 -1
- package/dist/cli/Serve.js +1 -1
- package/dist/cli/bin.js +2 -3
- package/dist/cli/serve/mcp/DevMcp.js +1 -1
- package/dist/cli/serve/mcp/McpTools.js +29 -1
- package/dist/cloud/CloudRemote.js +1 -1
- package/dist/components/Alert.js +2 -2
- package/dist/components/AppShell.js +2 -2
- package/dist/components/Badge.d.ts +3 -1
- package/dist/components/Badge.js +5 -3
- package/dist/components/Breadcrumbs.js +7 -7
- package/dist/components/Button.d.ts +6 -1
- package/dist/components/Button.js +16 -7
- package/dist/components/Calendar.js +20 -20
- package/dist/components/Checkbox.js +7 -7
- package/dist/components/CheckboxGroup.js +4 -4
- package/dist/components/Code.js +2 -2
- package/dist/components/Collapsible.js +3 -3
- package/dist/components/ColorSwatch.js +2 -2
- package/dist/components/ColorSwatchPicker.js +2 -2
- package/dist/components/ComboBox.js +7 -7
- package/dist/components/Command.js +14 -13
- package/dist/components/ContentCard.d.ts +3 -1
- package/dist/components/ContentCard.js +46 -24
- package/dist/components/ContentGrid.js +17 -17
- package/dist/components/DataList.js +2 -2
- 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.js +7 -7
- package/dist/components/DropZone.js +6 -6
- package/dist/components/DropdownMenu.js +9 -9
- package/dist/components/Empty.js +5 -5
- package/dist/components/Field.js +8 -8
- package/dist/components/List.js +27 -27
- package/dist/components/MediaPreview.js +5 -5
- package/dist/components/MultipleSelect.js +22 -22
- package/dist/components/NavRail.js +4 -4
- package/dist/components/NumberField.js +4 -4
- package/dist/components/Page.js +3 -3
- package/dist/components/Popover.js +2 -2
- package/dist/components/PreviewFrame.js +9 -9
- package/dist/components/RadioGroup.js +9 -9
- package/dist/components/Resizable.js +4 -4
- package/dist/components/SearchField.d.ts +6 -1
- package/dist/components/SearchField.js +5 -3
- package/dist/components/Select.js +13 -13
- package/dist/components/Sheet.d.ts +50 -0
- package/dist/components/Sheet.js +202 -0
- package/dist/components/Sidebar.js +4 -4
- package/dist/components/SortableList.d.ts +34 -30
- package/dist/components/SortableList.js +161 -264
- package/dist/components/Spinner.js +2 -2
- package/dist/components/Surface.js +2 -2
- package/dist/components/Switch.js +8 -8
- package/dist/components/Table.d.ts +3 -1
- package/dist/components/Table.js +57 -46
- package/dist/components/Tabs.js +5 -5
- package/dist/components/TagGroup.js +8 -8
- package/dist/components/TextField.js +10 -10
- package/dist/components/TimeField.js +6 -6
- package/dist/components/Timestamp.js +1 -1
- package/dist/components/ToggleGroup.js +2 -2
- package/dist/components/Toolbar.js +2 -2
- package/dist/components/Tooltip.js +5 -5
- package/dist/components/Tree.js +24 -24
- package/dist/components/internal/DateControl.js +12 -12
- package/dist/components/internal/ListBoxOption.js +6 -6
- package/dist/components/internal/Locale.js +2 -2
- package/dist/components/internal/PopoverSurface.js +4 -4
- package/dist/components/internal/SelectionCheckbox.js +5 -5
- package/dist/components/internal/Slot.js +1 -1
- package/dist/components/internal/ToggleButton.js +3 -3
- package/dist/components/internal/Trigger.js +2 -2
- package/dist/components/internal/sortableDrag.d.ts +19 -0
- package/dist/components/internal/sortableDrag.js +175 -0
- package/dist/components/internal/useDragDrop.js +2 -2
- package/dist/components.d.ts +1 -0
- package/dist/components.js +1 -0
- package/dist/core/Config.d.ts +6 -1
- package/dist/core/Document.d.ts +9 -1
- package/dist/core/Document.js +31 -6
- package/dist/core/Overview.d.ts +2 -4
- package/dist/core/TextDoc.d.ts +4 -0
- package/dist/core/Validation.d.ts +0 -1
- package/dist/core/Validation.js +0 -6
- package/dist/core/field/RichTextField.js +23 -4
- package/dist/core/media/MediaTypes.js +1 -1
- package/dist/core/source/GithubSource.d.ts +4 -0
- package/dist/core/source/GithubSource.js +127 -25
- package/dist/core/source/GithubUsage.d.ts +21 -0
- package/dist/core/source/GithubUsage.js +47 -0
- package/dist/dashboard/App.d.ts +2 -2
- package/dist/dashboard/App.js +5 -3
- package/dist/dashboard/ViewKeys.d.ts +1 -0
- package/dist/dashboard/ViewKeys.js +1 -0
- package/dist/dashboard/app/ActivityStatus.js +7 -7
- package/dist/dashboard/app/AuthView.js +5 -5
- package/dist/dashboard/app/BlockSheet.d.ts +50 -0
- package/dist/dashboard/app/BlockSheet.js +207 -0
- package/dist/dashboard/app/BlockSheetStoryFrame.d.ts +6 -0
- package/dist/dashboard/app/BlockSheetStoryFrame.js +29 -0
- package/dist/dashboard/app/CompactField.js +16 -16
- package/dist/dashboard/app/DashboardLayout.js +3 -1
- package/dist/dashboard/app/EntryFields.js +2 -2
- package/dist/dashboard/app/EntryHeader.js +33 -34
- package/dist/dashboard/app/EntryReferences.js +7 -7
- package/dist/dashboard/app/EntrySidebar.js +15 -15
- package/dist/dashboard/app/EntrySidebarPreview.js +3 -3
- package/dist/dashboard/app/EntryStatusIcon.d.ts +23 -0
- package/dist/dashboard/app/EntryStatusIcon.js +58 -0
- package/dist/dashboard/app/EntryValidationModal.d.ts +6 -5
- package/dist/dashboard/app/EntryValidationModal.js +20 -20
- package/dist/dashboard/app/Explorer.d.ts +3 -1
- package/dist/dashboard/app/Explorer.js +48 -44
- package/dist/dashboard/app/ExplorerBatchActions.d.ts +3 -1
- package/dist/dashboard/app/ExplorerBatchActions.js +68 -43
- package/dist/dashboard/app/ExplorerCards.js +7 -1
- package/dist/dashboard/app/ExplorerControls.js +6 -6
- package/dist/dashboard/app/ExplorerList.js +5 -5
- package/dist/dashboard/app/ExplorerModal.js +2 -2
- package/dist/dashboard/app/ExplorerTable.js +11 -8
- package/dist/dashboard/app/ImageEditor.js +28 -28
- package/dist/dashboard/app/ImagePicker.js +3 -13
- package/dist/dashboard/app/LinkPicker.js +28 -26
- package/dist/dashboard/app/LocaleMenu.js +4 -4
- package/dist/dashboard/app/MediaPreviewCell.js +1 -1
- package/dist/dashboard/app/MoveConfirmDialog.d.ts +5 -0
- package/dist/dashboard/app/MoveConfirmDialog.js +79 -0
- package/dist/dashboard/app/OverviewCell.js +8 -13
- package/dist/dashboard/app/PendingUploadsView.js +3 -3
- package/dist/dashboard/app/ReadOnlyBadge.d.ts +1 -1
- package/dist/dashboard/app/RemoveDialog.d.ts +17 -0
- package/dist/dashboard/app/{DeleteDialog.js → RemoveDialog.js} +67 -27
- package/dist/dashboard/app/RemoveRedirect.d.ts +7 -0
- package/dist/dashboard/app/RemoveRedirect.js +123 -0
- package/dist/dashboard/app/SearchBar.d.ts +9 -0
- package/dist/dashboard/app/SearchBar.js +31 -0
- package/dist/dashboard/app/SidebarTree.js +27 -72
- package/dist/dashboard/app/WorkspaceMenu.js +7 -7
- package/dist/dashboard/app/WorkspaceRoots.js +12 -12
- package/dist/dashboard/app/editor/FileEditor.js +14 -14
- package/dist/dashboard/app/modals/CreateEntry.js +3 -3
- package/dist/dashboard/app/pages/EntryPage.d.ts +2 -1
- package/dist/dashboard/app/pages/EntryPage.js +34 -15
- package/dist/dashboard/app/pages/SplashPage.js +37 -37
- package/dist/dashboard/app/pages/UsersPage.js +12 -12
- package/dist/dashboard/app/ui/DashboardModal.js +15 -14
- package/dist/dashboard/atoms/dashboard.js +1 -1
- package/dist/dashboard/atoms/entry.js +1 -14
- package/dist/dashboard/atoms/entryTable.js +1 -0
- package/dist/dashboard/atoms/explorer.d.ts +4 -0
- package/dist/dashboard/atoms/explorer.js +19 -7
- package/dist/dashboard/atoms/graph.js +1 -1
- package/dist/dashboard/atoms/link.js +5 -2
- package/dist/dashboard/atoms/move.d.ts +5 -0
- package/dist/dashboard/atoms/move.js +29 -3
- package/dist/dashboard/atoms/moveConfirmation.d.ts +34 -0
- package/dist/dashboard/atoms/moveConfirmation.js +95 -0
- package/dist/dashboard/atoms/nav.d.ts +9 -5
- package/dist/dashboard/atoms/nav.js +120 -45
- package/dist/dashboard/atoms/overview.d.ts +6 -5
- package/dist/dashboard/atoms/overview.js +2 -16
- package/dist/dashboard/atoms/remove.d.ts +112 -0
- package/dist/dashboard/atoms/remove.js +268 -0
- package/dist/dashboard/atoms/root.d.ts +2 -2
- package/dist/dashboard/atoms/root.js +41 -26
- package/dist/dashboard/atoms/user.js +1 -1
- package/dist/dashboard/atoms/utils.js +1 -1
- package/dist/dashboard/boot/Boot.js +25 -17
- package/dist/dashboard/boot/DashboardWorker.d.ts +1 -0
- package/dist/dashboard/boot/DashboardWorker.js +10 -46
- package/dist/dashboard/boot/LoadWorker.js +17 -10
- package/dist/dashboard/cms/EntryTable.js +8 -5
- package/dist/database/BrowserEntryStore.d.ts +12 -62
- package/dist/database/BrowserEntryStore.js +97 -196
- package/dist/database/DatabaseTables.js +2 -2
- package/dist/database/EntryDatabase.js +21 -1
- package/dist/database/EntryTransaction.js +0 -35
- package/dist/database/EntryUrlAliases.d.ts +4 -0
- package/dist/database/EntryUrlAliases.js +16 -10
- package/dist/database/driver/RuntimeDatabase.js +3 -3
- package/dist/database/driver/WasmDatabase.d.ts +5 -18
- package/dist/database/driver/WasmDatabase.js +15 -16
- package/dist/database/entry/EntryTable.js +2 -2
- package/dist/database/query/EntryQuery.js +1 -1
- package/dist/database/query/Relation.js +1 -1
- package/dist/database/query/Search.js +2 -2
- package/dist/field/code/CodeField.view.js +16 -16
- package/dist/field/link/EntryLink.d.ts +4 -4
- package/dist/field/link/FileLink.d.ts +4 -4
- package/dist/field/link/ImageLink.d.ts +4 -4
- package/dist/field/link/Link.d.ts +1 -1
- package/dist/field/link/LinkField.d.ts +5 -0
- package/dist/field/link/LinkField.view.js +244 -370
- package/dist/field/link/UrlLink.d.ts +4 -4
- package/dist/field/list/ListField.d.ts +2 -0
- package/dist/field/list/ListField.view.d.ts +15 -0
- package/dist/field/list/ListField.view.js +343 -301
- package/dist/field/metadata/MetadataField.d.ts +17 -7
- package/dist/field/metadata/MetadataField.js +14 -3
- package/dist/field/metadata/MetadataField.view.d.ts +1 -0
- package/dist/field/metadata/MetadataField.view.js +25 -21
- package/dist/field/richtext/RichTextBlock.d.ts +3 -1
- package/dist/field/richtext/RichTextBlock.js +177 -74
- package/dist/field/richtext/RichTextBlockHost.js +1 -1
- package/dist/field/richtext/RichTextExtensions.js +31 -31
- package/dist/field/richtext/RichTextField.view.js +14 -8
- package/dist/field/richtext/RichTextInsertMenu.js +43 -27
- package/dist/field/views.js +2 -0
- package/dist/index.css +490 -309
- package/dist/picker/url/UrlPicker.js +1 -0
- package/docs/components/badge.md +1 -0
- package/docs/components/button.md +1 -0
- package/docs/components/content-card.md +1 -0
- package/docs/components/search-field.md +1 -0
- package/docs/components/sortable-list.md +5 -20
- package/docs/components/table.md +1 -0
- package/docs/configuration.md +1 -1
- package/docs/deploy/self-host.md +1 -1
- package/docs/deploy.md +5 -4
- package/docs/fields.md +1 -1
- package/docs/index.md +1 -1
- package/docs/instant-publishing.md +7 -22
- package/docs/overviews.md +3 -4
- package/docs/upgrading.md +3 -2
- package/llms-full.txt +31 -55
- package/package.json +2 -3
- package/dist/core/db/EntryValidationError.d.ts +0 -13
- package/dist/core/db/EntryValidationError.js +0 -23
- package/dist/dashboard/app/DeleteDialog.d.ts +0 -11
- package/dist/dashboard/atoms/delete.d.ts +0 -68
- package/dist/dashboard/atoms/delete.js +0 -143
- package/dist/chunks/{chunk-MOWR65LM.js → chunk-PYDOMKJZ.js} +71 -71
package/dist/.mcp/later/dev.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"url":"http://localhost:1","pid":
|
|
1
|
+
{"url":"http://localhost:1","pid":3062}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"url":"http://localhost:
|
|
1
|
+
{"url":"http://localhost:36863","pid":3062}
|
package/dist/adapter/next/cms.js
CHANGED
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
applyPreview as applyPreviewUpdate,
|
|
9
9
|
decodePreviewRequest
|
|
10
10
|
} from "alinea/backend/resolver/ParsePreview";
|
|
11
|
+
import { dashboardUrl } from "./dashboardUrl.js";
|
|
11
12
|
import { createThrottledSync } from "alinea/backend/util/Syncable";
|
|
12
13
|
import { Client } from "alinea/core/Client";
|
|
13
14
|
import { CMS } from "alinea/core/CMS";
|
|
14
|
-
import { Config } from "alinea/core/Config";
|
|
15
15
|
import {
|
|
16
16
|
withDevelopmentKey
|
|
17
17
|
} from "alinea/core/Connection";
|
|
@@ -24,7 +24,7 @@ import { cache } from "react";
|
|
|
24
24
|
import { requestContext } from "./context.js";
|
|
25
25
|
import { cachedResolve, developmentRevision } from "./devCache.js";
|
|
26
26
|
import { RenderStats, summarizeQuery, timed } from "./renderStats.js";
|
|
27
|
-
import { syncIfStale } from "./syncCheck.js";
|
|
27
|
+
import { contentShaWindow, syncIfStale, tagContentSha } from "./syncCheck.js";
|
|
28
28
|
import { jsx } from "react/jsx-runtime";
|
|
29
29
|
var preValidatedRemote = { validate: false };
|
|
30
30
|
var NextCMS = class extends CMS {
|
|
@@ -66,6 +66,10 @@ var NextCMS = class extends CMS {
|
|
|
66
66
|
* returns a fresh holder, so nothing is recorded there.
|
|
67
67
|
*/
|
|
68
68
|
#render = cache(() => ({}));
|
|
69
|
+
/** Build renders join the content sha tag once, see `tagContentSha`. */
|
|
70
|
+
#tagContent = cache(
|
|
71
|
+
() => tagContentSha(contentShaWindow(this.config.syncInterval))
|
|
72
|
+
);
|
|
69
73
|
/** One content revision per render, see `developmentRevision`. */
|
|
70
74
|
#devRevision = cache(() => developmentRevision());
|
|
71
75
|
#isDraft = cache(async () => {
|
|
@@ -201,12 +205,19 @@ var NextCMS = class extends CMS {
|
|
|
201
205
|
const answer = () => stats && row ? stats.executed(row, () => db.resolve(request)) : db.resolve(request);
|
|
202
206
|
const syncInterval = request.disableSync ? Number.POSITIVE_INFINITY : request.syncInterval ?? this.config.syncInterval;
|
|
203
207
|
if (hasPreview) return answer();
|
|
204
|
-
if (
|
|
208
|
+
if (isBuild) {
|
|
209
|
+
await this.#tagContent();
|
|
210
|
+
} else {
|
|
205
211
|
const tracked = {
|
|
206
212
|
sha: db.sha,
|
|
207
213
|
syncWith: () => this.#syncDb(db, client, stats)
|
|
208
214
|
};
|
|
209
|
-
const settled = !isDraft && await syncIfStale(
|
|
215
|
+
const settled = !isDraft && await syncIfStale(
|
|
216
|
+
tracked,
|
|
217
|
+
client,
|
|
218
|
+
syncInterval,
|
|
219
|
+
contentShaWindow(this.config.syncInterval)
|
|
220
|
+
);
|
|
210
221
|
if (!settled)
|
|
211
222
|
await this.throttle(() => this.#syncDb(db, client, stats), syncInterval);
|
|
212
223
|
}
|
|
@@ -264,16 +275,13 @@ var NextCMS = class extends CMS {
|
|
|
264
275
|
if (!stats) return null;
|
|
265
276
|
const { default: dynamic } = await import("next/dynamic.js");
|
|
266
277
|
const { isDev, handlerUrl } = await requestContext(this.config);
|
|
267
|
-
let adminPath = Config.adminPath(this.config);
|
|
268
|
-
if (!adminPath.startsWith("/")) adminPath = `/${adminPath}`;
|
|
269
|
-
const dashboardUrl = isDev ? adminPath : new URL(`${adminPath}.html`, handlerUrl).href;
|
|
270
278
|
const NextPreviews = dynamic(() => import("./previews.js"), {
|
|
271
279
|
ssr: false
|
|
272
280
|
});
|
|
273
281
|
return /* @__PURE__ */ jsx(
|
|
274
282
|
NextPreviews,
|
|
275
283
|
{
|
|
276
|
-
dashboardUrl,
|
|
284
|
+
dashboardUrl: dashboardUrl(this.config, isDev, handlerUrl),
|
|
277
285
|
widget,
|
|
278
286
|
stats: widget && showStats ? stats.settled() : void 0,
|
|
279
287
|
workspace,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Config } from 'alinea/core/Config';
|
|
2
|
+
/**
|
|
3
|
+
* The dashboard as the site serves it: in development the site proxies the
|
|
4
|
+
* admin path to the dev server, in production it serves the generated file.
|
|
5
|
+
*/
|
|
6
|
+
export declare function dashboardUrl(config: Config, isDev: boolean, handlerUrl: URL | string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "../../chunks/chunk-CQ52GEHY.js";
|
|
2
|
+
|
|
3
|
+
// src/adapter/next/dashboardUrl.ts
|
|
4
|
+
import { Config } from "alinea/core/Config";
|
|
5
|
+
function dashboardUrl(config, isDev, handlerUrl) {
|
|
6
|
+
if (!isDev)
|
|
7
|
+
return new URL(`/${Config.dashboardFile(config)}`, handlerUrl).href;
|
|
8
|
+
const adminPath = Config.adminPath(config);
|
|
9
|
+
return adminPath.startsWith("/") ? adminPath : `/${adminPath}`;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
dashboardUrl
|
|
13
|
+
};
|
|
@@ -6,13 +6,26 @@ interface SyncableDB {
|
|
|
6
6
|
syncWith(source: RemoteSource, options?: SyncOptions): Promise<string>;
|
|
7
7
|
}
|
|
8
8
|
export declare const CONTENT_SHA_TAG = "alinea-content-sha";
|
|
9
|
+
/**
|
|
10
|
+
* How long renders share the latest content sha. Configured through the
|
|
11
|
+
* config's `syncInterval`, never a query's: all renders share one entry.
|
|
12
|
+
*/
|
|
13
|
+
export declare function contentShaWindow(syncInterval?: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* Tag a static render with the content sha without looking it up. Renders at
|
|
16
|
+
* build skip `syncIfStale`, so this is how their pages join the tag that
|
|
17
|
+
* commits revalidate, and get the same time-based safety net as pages
|
|
18
|
+
* rendered at runtime.
|
|
19
|
+
*/
|
|
20
|
+
export declare function tagContentSha(revalidate?: number): Promise<void>;
|
|
9
21
|
export declare function revalidateContentSha(): Promise<void>;
|
|
10
22
|
/**
|
|
11
23
|
* Sync the isolate-local db only when it is behind the shared content sha.
|
|
12
24
|
* Returns true when the freshness question is settled here (synced, forced,
|
|
13
25
|
* or explicitly disabled) and the caller can skip its throttled sync.
|
|
14
26
|
* Returns false when the sha could not be determined, so the caller falls
|
|
15
|
-
* back to its existing throttle behavior.
|
|
27
|
+
* back to its existing throttle behavior. `shaWindow` is how long renders
|
|
28
|
+
* share the latest sha, see `contentShaWindow`.
|
|
16
29
|
*/
|
|
17
|
-
export declare function syncIfStale(db: SyncableDB, client: Client, syncInterval?: number, options?: SyncOptions): Promise<boolean>;
|
|
30
|
+
export declare function syncIfStale(db: SyncableDB, client: Client, syncInterval?: number, shaWindow?: number, options?: SyncOptions): Promise<boolean>;
|
|
18
31
|
export {};
|
|
@@ -2,22 +2,27 @@ import "../../chunks/chunk-CQ52GEHY.js";
|
|
|
2
2
|
|
|
3
3
|
// src/adapter/next/syncCheck.ts
|
|
4
4
|
var CONTENT_SHA_TAG = "alinea-content-sha";
|
|
5
|
-
var SHA_REVALIDATE_SECONDS =
|
|
5
|
+
var SHA_REVALIDATE_SECONDS = 300;
|
|
6
|
+
function contentShaWindow(syncInterval) {
|
|
7
|
+
if (syncInterval !== void 0 && syncInterval > 0 && Number.isFinite(syncInterval))
|
|
8
|
+
return syncInterval;
|
|
9
|
+
return SHA_REVALIDATE_SECONDS;
|
|
10
|
+
}
|
|
6
11
|
var inflight;
|
|
7
|
-
function latestSha(client) {
|
|
12
|
+
function latestSha(client, revalidate) {
|
|
8
13
|
if (inflight) return inflight;
|
|
9
|
-
inflight = loadLatestSha(client).finally(() => {
|
|
14
|
+
inflight = loadLatestSha(client, revalidate).finally(() => {
|
|
10
15
|
inflight = void 0;
|
|
11
16
|
});
|
|
12
17
|
return inflight;
|
|
13
18
|
}
|
|
14
|
-
async function loadLatestSha(client) {
|
|
19
|
+
async function loadLatestSha(client, revalidate) {
|
|
15
20
|
try {
|
|
16
21
|
const { unstable_cache } = await import("next/cache.js");
|
|
17
22
|
const getSha = unstable_cache(
|
|
18
23
|
() => client.getSha(),
|
|
19
24
|
["alinea-content-sha"],
|
|
20
|
-
{ revalidate
|
|
25
|
+
{ revalidate, tags: [CONTENT_SHA_TAG] }
|
|
21
26
|
);
|
|
22
27
|
return await getSha();
|
|
23
28
|
} catch {
|
|
@@ -28,26 +33,38 @@ async function loadLatestSha(client) {
|
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
}
|
|
36
|
+
async function tagContentSha(revalidate = SHA_REVALIDATE_SECONDS) {
|
|
37
|
+
try {
|
|
38
|
+
const { unstable_cache } = await import("next/cache.js");
|
|
39
|
+
await unstable_cache(async () => true, ["alinea-content-sha-tag"], {
|
|
40
|
+
revalidate,
|
|
41
|
+
tags: [CONTENT_SHA_TAG]
|
|
42
|
+
})();
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
31
46
|
async function revalidateContentSha() {
|
|
32
47
|
try {
|
|
33
48
|
const { revalidateTag } = await import("next/cache.js");
|
|
34
|
-
revalidateTag(CONTENT_SHA_TAG,
|
|
49
|
+
revalidateTag(CONTENT_SHA_TAG, "max");
|
|
35
50
|
} catch {
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
|
-
async function syncIfStale(db, client, syncInterval, options) {
|
|
53
|
+
async function syncIfStale(db, client, syncInterval, shaWindow = SHA_REVALIDATE_SECONDS, options) {
|
|
39
54
|
if (syncInterval === Number.POSITIVE_INFINITY) return true;
|
|
40
55
|
if (syncInterval === 0) {
|
|
41
56
|
await db.syncWith(client, options);
|
|
42
57
|
return true;
|
|
43
58
|
}
|
|
44
|
-
const expected = await latestSha(client);
|
|
59
|
+
const expected = await latestSha(client, shaWindow);
|
|
45
60
|
if (!expected) return false;
|
|
46
61
|
if (expected !== await db.sha) await db.syncWith(client, options);
|
|
47
62
|
return true;
|
|
48
63
|
}
|
|
49
64
|
export {
|
|
50
65
|
CONTENT_SHA_TAG,
|
|
66
|
+
contentShaWindow,
|
|
51
67
|
revalidateContentSha,
|
|
52
|
-
syncIfStale
|
|
68
|
+
syncIfStale,
|
|
69
|
+
tagContentSha
|
|
53
70
|
};
|
package/dist/backend/Handler.js
CHANGED
|
@@ -328,7 +328,11 @@ function createHandler({
|
|
|
328
328
|
}
|
|
329
329
|
if (action === HandleAction.Blob && request.method === "POST") {
|
|
330
330
|
const { shas } = object({ shas: array(string) })(await body);
|
|
331
|
-
|
|
331
|
+
let tree = await local.source.getTree();
|
|
332
|
+
if (!dev && shas.some((sha) => !tree.hasSha(sha))) {
|
|
333
|
+
await syncForRead(cnx);
|
|
334
|
+
tree = await local.source.getTree();
|
|
335
|
+
}
|
|
332
336
|
const fromLocal = [];
|
|
333
337
|
const fromRemote = [];
|
|
334
338
|
for (const sha of shas) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CommitApi, SyncApi } from 'alinea/core/Connection';
|
|
2
|
+
/**
|
|
3
|
+
* Content blobs kept where every handler instance finds them, such as the
|
|
4
|
+
* backend database: an instance that is behind reads what another one
|
|
5
|
+
* fetched or committed, instead of fetching it from the remote again.
|
|
6
|
+
*/
|
|
7
|
+
export interface BlobStore {
|
|
8
|
+
/** The stored blobs among `shas`. */
|
|
9
|
+
readBlobs(shas: ReadonlyArray<string>): Promise<Map<string, Uint8Array>>;
|
|
10
|
+
/** Keep blobs, found again by their sha. */
|
|
11
|
+
storeBlobs(blobs: ReadonlyArray<[sha: string, blob: Uint8Array]>): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function isBlobStore<T extends object>(value: T): value is T & BlobStore;
|
|
14
|
+
/**
|
|
15
|
+
* Answer from the store what it has, and fetch the rest from the remote,
|
|
16
|
+
* storing it. The store only saves requests: when it fails, the remote
|
|
17
|
+
* answers instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function storedBlobs(store: BlobStore, getBlobs: SyncApi['getBlobs']): SyncApi['getBlobs'];
|
|
20
|
+
/** Commit through the remote, and store the committed content. */
|
|
21
|
+
export declare function storedWrites(store: BlobStore, write: CommitApi['write']): CommitApi['write'];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "../../chunks/chunk-CQ52GEHY.js";
|
|
2
|
+
|
|
3
|
+
// src/backend/api/BlobStore.ts
|
|
4
|
+
import { hashBlob } from "alinea/core/source/GitUtils";
|
|
5
|
+
var storeBatch = 100;
|
|
6
|
+
function isBlobStore(value) {
|
|
7
|
+
return "readBlobs" in value && typeof value.readBlobs === "function" && "storeBlobs" in value && typeof value.storeBlobs === "function";
|
|
8
|
+
}
|
|
9
|
+
function storedBlobs(store, getBlobs) {
|
|
10
|
+
return async function* (shas, options) {
|
|
11
|
+
const stored = await store.readBlobs(shas).catch((error) => {
|
|
12
|
+
console.warn("Alinea could not read stored blobs", error);
|
|
13
|
+
return /* @__PURE__ */ new Map();
|
|
14
|
+
});
|
|
15
|
+
for (const sha of shas) {
|
|
16
|
+
const blob = stored.get(sha);
|
|
17
|
+
if (blob) yield [sha, blob];
|
|
18
|
+
}
|
|
19
|
+
const missing = shas.filter((sha) => !stored.has(sha));
|
|
20
|
+
if (missing.length === 0) return;
|
|
21
|
+
let fetched = Array();
|
|
22
|
+
const keep = async () => {
|
|
23
|
+
const blobs = fetched;
|
|
24
|
+
fetched = [];
|
|
25
|
+
if (blobs.length > 0) await keepBlobs(store, blobs);
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
for await (const entry of getBlobs(missing, options)) {
|
|
29
|
+
fetched.push(entry);
|
|
30
|
+
if (fetched.length >= storeBatch) await keep();
|
|
31
|
+
yield entry;
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
await keep();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function storedWrites(store, write) {
|
|
39
|
+
return async (request) => {
|
|
40
|
+
const result = await write(request);
|
|
41
|
+
const encoder = new TextEncoder();
|
|
42
|
+
const blobs = Array();
|
|
43
|
+
for (const change of request.changes) {
|
|
44
|
+
if (change.op !== "addContent") continue;
|
|
45
|
+
const blob = encoder.encode(change.contents);
|
|
46
|
+
if (await hashBlob(blob) === change.sha) blobs.push([change.sha, blob]);
|
|
47
|
+
}
|
|
48
|
+
if (blobs.length > 0) await keepBlobs(store, blobs);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function keepBlobs(store, blobs) {
|
|
53
|
+
try {
|
|
54
|
+
await store.storeBlobs(blobs);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn("Alinea could not store blobs", error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
isBlobStore,
|
|
61
|
+
storedBlobs,
|
|
62
|
+
storedWrites
|
|
63
|
+
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
driver_exports
|
|
3
|
-
} from "../../chunks/chunk-
|
|
4
|
-
import "../../chunks/chunk-
|
|
5
|
-
import "../../chunks/chunk-
|
|
6
|
-
import "../../chunks/chunk-
|
|
7
|
-
import "../../chunks/chunk-
|
|
3
|
+
} from "../../chunks/chunk-4ZA6KZHS.js";
|
|
4
|
+
import "../../chunks/chunk-VA6NC3MI.js";
|
|
5
|
+
import "../../chunks/chunk-A2I6MCCS.js";
|
|
6
|
+
import "../../chunks/chunk-RAR44T5O.js";
|
|
7
|
+
import "../../chunks/chunk-PTYZMV5G.js";
|
|
8
8
|
import "../../chunks/chunk-N47Y62CI.js";
|
|
9
9
|
import "../../chunks/chunk-QBPYWU6C.js";
|
|
10
10
|
import "../../chunks/chunk-2BJ2IFQM.js";
|
|
11
11
|
import "../../chunks/chunk-XCZXR3C2.js";
|
|
12
|
-
import "../../chunks/chunk-
|
|
12
|
+
import "../../chunks/chunk-PYDOMKJZ.js";
|
|
13
13
|
import "../../chunks/chunk-76VMZTLN.js";
|
|
14
14
|
import "../../chunks/chunk-2PIFHEYR.js";
|
|
15
15
|
import "../../chunks/chunk-K62JP6HR.js";
|
|
@@ -20,6 +20,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
|
|
|
20
20
|
// src/backend/api/CreateBackend.ts
|
|
21
21
|
import { assert } from "alinea/core/util/Assert";
|
|
22
22
|
import { BasicAuth } from "./BasicAuth.js";
|
|
23
|
+
import { isBlobStore, storedBlobs, storedWrites } from "./BlobStore.js";
|
|
23
24
|
import { DatabaseApi } from "./DatabaseApi.js";
|
|
24
25
|
import { GithubApi } from "./GithubApi.js";
|
|
25
26
|
import { OAuth2 } from "./OAuth2.js";
|
|
@@ -65,13 +66,14 @@ function composeBackend(...impl) {
|
|
|
65
66
|
throw new Error(`Backend does not implement ${name}`);
|
|
66
67
|
};
|
|
67
68
|
};
|
|
69
|
+
const blobStore = impl.find(isBlobStore);
|
|
68
70
|
return {
|
|
69
71
|
authenticate: call("authenticate"),
|
|
70
72
|
verify: call("verify"),
|
|
71
73
|
capabilities: call("capabilities"),
|
|
72
74
|
getTreeIfDifferent: call("getTreeIfDifferent"),
|
|
73
|
-
getBlobs: call("getBlobs"),
|
|
74
|
-
write: call("write"),
|
|
75
|
+
getBlobs: blobStore ? storedBlobs(blobStore, call("getBlobs")) : call("getBlobs"),
|
|
76
|
+
write: blobStore ? storedWrites(blobStore, call("write")) : call("write"),
|
|
75
77
|
revisions: call("revisions"),
|
|
76
78
|
revisionData: call("revisionData"),
|
|
77
79
|
getDraft: call("getDraft"),
|
|
@@ -2,10 +2,11 @@ import type { DraftsApi, RequestContext, UploadResponse, UploadsApi, UserApi } f
|
|
|
2
2
|
import { type Draft, type DraftKey } from 'alinea/core/Draft';
|
|
3
3
|
import type { User, UserInput } from 'alinea/core/User';
|
|
4
4
|
import { type Database } from 'rado';
|
|
5
|
+
import type { BlobStore } from './BlobStore.js';
|
|
5
6
|
export interface DatabaseOptions {
|
|
6
7
|
db: Database;
|
|
7
8
|
}
|
|
8
|
-
export declare class DatabaseApi implements DraftsApi, UploadsApi, UserApi {
|
|
9
|
+
export declare class DatabaseApi implements DraftsApi, UploadsApi, UserApi, BlobStore {
|
|
9
10
|
#private;
|
|
10
11
|
constructor(context: RequestContext, { db }: DatabaseOptions);
|
|
11
12
|
getDraft(draftKey: DraftKey): Promise<Draft | undefined>;
|
|
@@ -13,6 +14,8 @@ export declare class DatabaseApi implements DraftsApi, UploadsApi, UserApi {
|
|
|
13
14
|
prepareUpload(file: string): Promise<UploadResponse>;
|
|
14
15
|
handleUpload(entryId: string, file: Blob): Promise<void>;
|
|
15
16
|
previewUpload(entryId: string): Promise<Response>;
|
|
17
|
+
readBlobs(shas: ReadonlyArray<string>): Promise<Map<string, Uint8Array>>;
|
|
18
|
+
storeBlobs(blobs: ReadonlyArray<[sha: string, blob: Uint8Array]>): Promise<void>;
|
|
16
19
|
enrichUser(user: User): Promise<User>;
|
|
17
20
|
listUsers(): Promise<Array<User>>;
|
|
18
21
|
createUser(user: UserInput): Promise<User>;
|
|
@@ -7,16 +7,21 @@ import {
|
|
|
7
7
|
integer,
|
|
8
8
|
text,
|
|
9
9
|
varchar
|
|
10
|
-
} from "../../chunks/chunk-
|
|
10
|
+
} from "../../chunks/chunk-5H4D5JQL.js";
|
|
11
11
|
import {
|
|
12
12
|
Builder
|
|
13
13
|
} from "../../chunks/chunk-2BJ2IFQM.js";
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
and,
|
|
16
|
+
eq,
|
|
17
|
+
inArray,
|
|
18
|
+
lt
|
|
16
19
|
} from "../../chunks/chunk-XCZXR3C2.js";
|
|
17
20
|
import {
|
|
21
|
+
Column,
|
|
22
|
+
ColumnType,
|
|
18
23
|
table
|
|
19
|
-
} from "../../chunks/chunk-
|
|
24
|
+
} from "../../chunks/chunk-PYDOMKJZ.js";
|
|
20
25
|
import "../../chunks/chunk-76VMZTLN.js";
|
|
21
26
|
import "../../chunks/chunk-2PIFHEYR.js";
|
|
22
27
|
import "../../chunks/chunk-K62JP6HR.js";
|
|
@@ -33,6 +38,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
|
|
|
33
38
|
import { createId } from "alinea/core/Id";
|
|
34
39
|
import { assert } from "alinea/core/util/Assert";
|
|
35
40
|
import { basename, extname } from "alinea/core/util/Paths";
|
|
41
|
+
import { chunks } from "alinea/core/util/Arrays";
|
|
36
42
|
import { slugify } from "alinea/core/util/Slugs";
|
|
37
43
|
import { HandleAction } from "../HandleAction.js";
|
|
38
44
|
var UploadTable = table("alinea_upload", {
|
|
@@ -48,7 +54,17 @@ var UserRoleTable = table("alinea_user_role", {
|
|
|
48
54
|
userId: integer().notNull().references(UserTable.id),
|
|
49
55
|
role: text().notNull()
|
|
50
56
|
});
|
|
51
|
-
var
|
|
57
|
+
var BlobTable = table("alinea_blob", {
|
|
58
|
+
sha: varchar(void 0, { length: 40 }).primaryKey(),
|
|
59
|
+
content: largeBlob().notNull(),
|
|
60
|
+
/** When the blob was last stored or read, in seconds */
|
|
61
|
+
usedAt: integer().notNull()
|
|
62
|
+
});
|
|
63
|
+
var tables = [UploadTable, UserTable, UserRoleTable, BlobTable];
|
|
64
|
+
var blobsPerStatement = 50;
|
|
65
|
+
var day = 24 * 60 * 60;
|
|
66
|
+
var blobRetention = 30 * day;
|
|
67
|
+
var blobCleanups = /* @__PURE__ */ new WeakMap();
|
|
52
68
|
var preparedDatabases = /* @__PURE__ */ new WeakMap();
|
|
53
69
|
var selectUser = {
|
|
54
70
|
...UserTable,
|
|
@@ -108,6 +124,35 @@ var DatabaseApi = class {
|
|
|
108
124
|
}
|
|
109
125
|
});
|
|
110
126
|
}
|
|
127
|
+
async readBlobs(shas) {
|
|
128
|
+
const db = await this.#db;
|
|
129
|
+
const found = /* @__PURE__ */ new Map();
|
|
130
|
+
for (const batch of chunks(shas, blobsPerStatement)) {
|
|
131
|
+
const rows = await db.select({ sha: BlobTable.sha, content: BlobTable.content }).from(BlobTable).where(inArray(BlobTable.sha, batch));
|
|
132
|
+
for (const row of rows) found.set(row.sha, row.content);
|
|
133
|
+
}
|
|
134
|
+
const now = seconds();
|
|
135
|
+
for (const batch of chunks([...found.keys()], blobsPerStatement))
|
|
136
|
+
await renewBlobs(db, batch, now);
|
|
137
|
+
await removeUnusedBlobs(db, now);
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
async storeBlobs(blobs) {
|
|
141
|
+
const db = await this.#db;
|
|
142
|
+
const now = seconds();
|
|
143
|
+
const unique = [...new Map(blobs)];
|
|
144
|
+
for (const batch of chunks(unique, blobsPerStatement)) {
|
|
145
|
+
const shas = batch.map(([sha]) => sha);
|
|
146
|
+
const stored = new Set(
|
|
147
|
+
await db.select(BlobTable.sha).from(BlobTable).where(inArray(BlobTable.sha, shas))
|
|
148
|
+
);
|
|
149
|
+
await renewBlobs(db, [...stored], now);
|
|
150
|
+
const rows = batch.filter(([sha]) => !stored.has(sha)).map(([sha, content]) => ({ sha, content, usedAt: now }));
|
|
151
|
+
if (rows.length === 0) continue;
|
|
152
|
+
await db.insert(BlobTable).values(rows).catch(() => insertMissingBlobs(db, rows));
|
|
153
|
+
}
|
|
154
|
+
await removeUnusedBlobs(db, now);
|
|
155
|
+
}
|
|
111
156
|
async enrichUser(user) {
|
|
112
157
|
const normalized = normalizeUser(user);
|
|
113
158
|
const found = await this.#getUser(normalized.email);
|
|
@@ -191,6 +236,45 @@ function prepareDatabase(db) {
|
|
|
191
236
|
preparedDatabases.set(db, prepared);
|
|
192
237
|
return prepared;
|
|
193
238
|
}
|
|
239
|
+
function largeBlob() {
|
|
240
|
+
return new Column({
|
|
241
|
+
type: new ColumnType(
|
|
242
|
+
"blob",
|
|
243
|
+
[],
|
|
244
|
+
sql.universal({
|
|
245
|
+
postgres: sql`bytea`,
|
|
246
|
+
mysql: sql`longblob`,
|
|
247
|
+
default: sql`blob`
|
|
248
|
+
})
|
|
249
|
+
),
|
|
250
|
+
mapFromDriverValue(value) {
|
|
251
|
+
return new Uint8Array(value);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async function renewBlobs(db, shas, now) {
|
|
256
|
+
if (shas.length === 0) return;
|
|
257
|
+
await db.update(BlobTable).set({ usedAt: now }).where(and(inArray(BlobTable.sha, shas), lt(BlobTable.usedAt, now - day)));
|
|
258
|
+
}
|
|
259
|
+
async function insertMissingBlobs(db, rows) {
|
|
260
|
+
for (const row of rows) {
|
|
261
|
+
try {
|
|
262
|
+
await db.insert(BlobTable).values(row);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const stored = await db.select(BlobTable.sha).from(BlobTable).where(eq(BlobTable.sha, row.sha)).get();
|
|
265
|
+
if (!stored) throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function removeUnusedBlobs(db, now) {
|
|
270
|
+
const last = blobCleanups.get(db) ?? 0;
|
|
271
|
+
if (now - last < 60 * 60) return;
|
|
272
|
+
blobCleanups.set(db, now);
|
|
273
|
+
await db.delete(BlobTable).where(lt(BlobTable.usedAt, now - blobRetention));
|
|
274
|
+
}
|
|
275
|
+
function seconds() {
|
|
276
|
+
return Math.floor(Date.now() / 1e3);
|
|
277
|
+
}
|
|
194
278
|
async function enablePostgresRowLevelSecurity(db) {
|
|
195
279
|
if (db.dialect.runtime !== "postgres") return;
|
|
196
280
|
for (const table2 of tables) {
|