smoodly 0.0.51 → 0.0.52
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/README.md +14 -13
- package/dist/admin/auth-config.d.ts +6 -5
- package/dist/admin/auth.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/browser/fetch-op.js +3 -2
- package/dist/admin/browser/main.js +6 -2
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +14 -11
- package/dist/admin/delivery-mode.d.ts +1 -1
- package/dist/admin/delivery-mode.js +2 -2
- package/dist/admin/drift.d.ts +0 -2
- package/dist/admin/drift.js +1 -1
- package/dist/admin/editor/EditorView.d.ts +0 -3
- package/dist/admin/editor/EditorView.js +11 -16
- package/dist/admin/editor/FloatingToolbar.d.ts +0 -1
- package/dist/admin/editor/FloatingToolbar.js +1 -1
- package/dist/admin/editor/SeoSettings.d.ts +0 -2
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/editor/canvas-address.d.ts +8 -0
- package/dist/admin/editor/canvas-address.js +18 -2
- package/dist/admin/editor/canvas-diagnosis.d.ts +2 -0
- package/dist/admin/editor/canvas-diagnosis.js +3 -0
- package/dist/admin/editor/inline-dom.d.ts +0 -4
- package/dist/admin/editor/inline-dom.js +1 -1
- package/dist/admin/editor/preview-grant.d.ts +6 -0
- package/dist/admin/editor/preview-grant.js +25 -0
- package/dist/admin/editor/useCanvasDiagnosis.js +2 -2
- package/dist/admin/files.generated.js +1 -1
- package/dist/admin/fixed-nodes.d.ts +0 -6
- package/dist/admin/fixed-nodes.js +1 -81
- package/dist/admin/forms/CropEditor.d.ts +4 -2
- package/dist/admin/forms/CropEditor.js +58 -5
- package/dist/admin/forms/ImagePreview.d.ts +3 -1
- package/dist/admin/forms/ImagePreview.js +9 -3
- package/dist/admin/forms/MediaGrid.js +2 -1
- package/dist/admin/forms/MediaPicker.js +9 -6
- package/dist/admin/forms/MediaWidget.js +11 -9
- package/dist/admin/forms/assets-context.d.ts +7 -3
- package/dist/admin/forms/assets-context.js +5 -3
- package/dist/admin/forms/crop-editor.d.ts +24 -2
- package/dist/admin/forms/crop-editor.js +36 -2
- package/dist/admin/forms/date-picker.d.ts +0 -1
- package/dist/admin/forms/date-picker.js +1 -1
- package/dist/admin/forms/tabs.d.ts +0 -1
- package/dist/admin/forms/tabs.js +1 -1
- package/dist/admin/forms/upload.d.ts +18 -11
- package/dist/admin/forms/upload.js +62 -24
- package/dist/admin/global-items.d.ts +0 -3
- package/dist/admin/global-items.js +0 -54
- package/dist/admin/handler.d.ts +22 -28
- package/dist/admin/handler.js +146 -47
- package/dist/admin/health.d.ts +0 -4
- package/dist/admin/index.d.ts +5 -8
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +36 -22
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +43 -0
- package/dist/admin/session-flow.js +94 -0
- package/dist/admin/shell/AdminApp.d.ts +8 -3
- package/dist/admin/shell/AdminApp.js +37 -58
- package/dist/admin/shell/ContentHealthPage.js +1 -3
- package/dist/admin/shell/GlobalFields.d.ts +0 -3
- package/dist/admin/shell/GlobalFields.js +1 -1
- package/dist/admin/shell/IconRail.js +5 -4
- package/dist/admin/shell/MediaScreen.js +9 -6
- package/dist/admin/shell/MembersPage.d.ts +1 -2
- package/dist/admin/shell/MembersPage.js +19 -90
- package/dist/admin/shell/SettingsView.d.ts +1 -2
- package/dist/admin/shell/SettingsView.js +2 -2
- package/dist/admin/shell/SignedOutView.d.ts +5 -0
- package/dist/admin/shell/SignedOutView.js +9 -0
- package/dist/admin/shell/console-links.d.ts +5 -0
- package/dist/admin/shell/console-links.js +11 -0
- package/dist/admin/shell/members-list.d.ts +1 -14
- package/dist/admin/shell/members-list.js +2 -23
- package/dist/admin/shell/pages-tree.d.ts +0 -2
- package/dist/admin/shell/pages-tree.js +1 -1
- package/dist/admin/shell/presence-socket.d.ts +10 -0
- package/dist/admin/shell/presence-socket.js +191 -0
- package/dist/admin/shell/presence.d.ts +6 -18
- package/dist/admin/shell/presence.js +4 -11
- package/dist/admin/shell/session.d.ts +20 -12
- package/dist/admin/shell/session.js +113 -47
- package/dist/admin/ui/LocaleSwitcher.d.ts +0 -4
- package/dist/admin/ui/LocaleSwitcher.js +2 -2
- package/dist/admin/ui/primitives.d.ts +4 -14
- package/dist/admin/ui/primitives.js +5 -8
- package/dist/admin/ui/theme.js +13 -2
- package/dist/admin/validate.js +10 -1
- package/dist/api-client.d.ts +26 -0
- package/dist/api-client.js +107 -0
- package/dist/assemble.d.ts +4 -41
- package/dist/assemble.js +4 -302
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- package/dist/astro/components/Head.astro +4 -1
- package/dist/astro/components/Image.astro +12 -17
- package/dist/astro/components/Video.astro +5 -2
- package/dist/astro/head.d.ts +3 -0
- package/dist/astro/head.js +24 -0
- package/dist/astro/index.js +54 -25
- package/dist/astro/locale-paths.d.ts +5 -2
- package/dist/astro/locale-paths.js +10 -2
- package/dist/astro/routes/admin.js +9 -7
- package/dist/astro/runtime.d.ts +2 -1
- package/dist/astro/runtime.js +4 -3
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +2 -2
- package/dist/astro/static-media-node.d.ts +4 -0
- package/dist/astro/static-media-node.js +25 -0
- package/dist/astro/static-media.d.ts +17 -0
- package/dist/astro/static-media.js +55 -0
- package/dist/astro/static.d.ts +5 -10
- package/dist/astro/static.js +13 -29
- package/dist/canvas-marker.d.ts +14 -0
- package/dist/canvas-marker.js +36 -0
- package/dist/cli/api.d.ts +14 -0
- package/dist/cli/api.js +48 -0
- package/dist/cli/app-env.d.ts +2 -0
- package/dist/cli/app-env.js +20 -0
- package/dist/cli/export.d.ts +16 -2
- package/dist/cli/export.js +52 -35
- package/dist/cli/import.d.ts +7 -11
- package/dist/cli/import.js +50 -54
- package/dist/cli/load-config.d.ts +0 -1
- package/dist/cli/load-config.js +1 -1
- package/dist/cli/main.d.ts +4 -12
- package/dist/cli/main.js +26 -72
- package/dist/cli/push.d.ts +6 -0
- package/dist/cli/push.js +31 -0
- package/dist/collections.d.ts +0 -1
- package/dist/collections.js +1 -1
- package/dist/config.d.ts +20 -5
- package/dist/config.js +18 -1
- package/dist/content/content-api.d.ts +22 -23
- package/dist/content/content-api.js +46 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/deploy-store.d.ts +19 -0
- package/dist/deploy-store.js +1 -0
- package/dist/entry-store.d.ts +3 -89
- package/dist/entry-store.js +2 -550
- package/dist/env.d.ts +27 -20
- package/dist/env.js +38 -50
- package/dist/fields.d.ts +11 -3
- package/dist/fields.js +33 -2
- package/dist/global-id.d.ts +2 -0
- package/dist/global-id.js +4 -2
- package/dist/image/SmoodlyImage.d.ts +9 -5
- package/dist/image/SmoodlyImage.js +29 -15
- package/dist/image/SmoodlyVideo.d.ts +1 -1
- package/dist/image/SmoodlyVideo.js +3 -2
- package/dist/image/index.d.ts +2 -3
- package/dist/image/index.js +0 -2
- package/dist/image/markup.d.ts +53 -0
- package/dist/image/markup.js +111 -0
- package/dist/index.d.ts +12 -22
- package/dist/index.js +7 -20
- package/dist/media-cut.d.ts +44 -0
- package/dist/media-cut.js +70 -0
- package/dist/media.d.ts +57 -3
- package/dist/media.js +125 -15
- package/dist/member-store.d.ts +3 -22
- package/dist/member-store.js +1 -53
- package/dist/next/admin-route.d.ts +1 -1
- package/dist/next/admin-route.js +5 -3
- package/dist/next/meta.js +2 -3
- package/dist/next/page-renderer.d.ts +2 -2
- package/dist/next/page-renderer.js +22 -11
- package/dist/next/queries.d.ts +2 -2
- package/dist/path-index.d.ts +0 -24
- package/dist/path-index.js +0 -98
- package/dist/push.d.ts +30 -0
- package/dist/push.js +52 -0
- package/dist/read-counter.d.ts +2 -0
- package/dist/read-counter.js +6 -0
- package/dist/reads.d.ts +7 -7
- package/dist/reads.js +3 -0
- package/dist/registry-json.d.ts +11 -0
- package/dist/registry-json.js +81 -0
- package/dist/render.d.ts +0 -1
- package/dist/render.js +1 -1
- package/dist/resolve.d.ts +12 -6
- package/dist/resolve.js +11 -8
- package/dist/revalidate.d.ts +8 -1
- package/dist/revalidate.js +8 -1
- package/dist/richtext/index.d.ts +1 -1
- package/dist/section-wrapper.d.ts +1 -1
- package/dist/section-wrapper.js +0 -9
- package/dist/signed-message.d.ts +26 -0
- package/dist/signed-message.js +103 -0
- package/dist/site.d.ts +59 -18
- package/dist/site.js +21 -252
- package/dist/space-transfer.d.ts +5 -2
- package/dist/space-transfer.js +17 -5
- package/dist/store.d.ts +2 -80
- package/dist/store.js +3 -489
- package/dist/toolset.d.ts +2 -1
- package/dist/toolset.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -15
- package/dist/admin/ops-impl.d.ts +0 -51
- package/dist/admin/ops-impl.js +0 -1403
- package/dist/admin/shell/InviteModal.d.ts +0 -6
- package/dist/admin/shell/InviteModal.js +0 -28
- package/dist/admin/shell/LoginView.d.ts +0 -5
- package/dist/admin/shell/LoginView.js +0 -80
- package/dist/admin/shell/SchemaNotice.d.ts +0 -4
- package/dist/admin/shell/SchemaNotice.js +0 -4
- package/dist/admin/shell/SetPasswordView.d.ts +0 -7
- package/dist/admin/shell/SetPasswordView.js +0 -41
- package/dist/admin/shell/presence-realtime.d.ts +0 -3
- package/dist/admin/shell/presence-realtime.js +0 -32
- package/dist/admin/supabase-auth.d.ts +0 -7
- package/dist/admin/supabase-auth.js +0 -30
- package/dist/astro/deploy-deps.d.ts +0 -10
- package/dist/astro/deploy-deps.js +0 -8
- package/dist/astro/image.d.ts +0 -27
- package/dist/astro/image.js +0 -29
- package/dist/bind.d.ts +0 -19
- package/dist/bind.js +0 -34
- package/dist/cli/add-admin.d.ts +0 -11
- package/dist/cli/add-admin.js +0 -25
- package/dist/cli/load-collections.d.ts +0 -2
- package/dist/cli/load-collections.js +0 -27
- package/dist/cli/migrate.d.ts +0 -43
- package/dist/cli/migrate.js +0 -138
- package/dist/cli/rebuild.d.ts +0 -23
- package/dist/cli/rebuild.js +0 -22
- package/dist/cli/space-io.d.ts +0 -41
- package/dist/cli/space-io.js +0 -84
- package/dist/client.d.ts +0 -11
- package/dist/client.js +0 -54
- package/dist/current-space.d.ts +0 -4
- package/dist/current-space.js +0 -9
- package/dist/document-stamp.d.ts +0 -7
- package/dist/document-stamp.js +0 -48
- package/dist/image/loaders.d.ts +0 -15
- package/dist/image/loaders.js +0 -5
- package/dist/image/next-config.d.ts +0 -18
- package/dist/image/next-config.js +0 -32
- package/dist/image/remote-pattern.d.ts +0 -1
- package/dist/image/remote-pattern.js +0 -5
- package/dist/migrations.d.ts +0 -37
- package/dist/migrations.js +0 -73
- package/dist/project.d.ts +0 -88
- package/dist/project.js +0 -231
- package/dist/published.d.ts +0 -142
- package/dist/published.js +0 -164
- package/dist/ref-index.d.ts +0 -19
- package/dist/ref-index.js +0 -0
- package/dist/schema-read.d.ts +0 -23
- package/dist/schema-read.js +0 -50
- package/dist/schema-version.d.ts +0 -19
- package/dist/schema-version.js +0 -34
- package/dist/sql-space.d.ts +0 -44
- package/dist/sql-space.js +0 -289
- package/dist/sql.d.ts +0 -15
- package/dist/sql.js +0 -556
- package/dist/supabase-asset-store.d.ts +0 -21
- package/dist/supabase-asset-store.js +0 -170
- package/dist/supabase-entry-store.d.ts +0 -92
- package/dist/supabase-entry-store.js +0 -824
- package/dist/supabase-member-store.d.ts +0 -21
- package/dist/supabase-member-store.js +0 -96
- package/dist/supabase-path-index.d.ts +0 -30
- package/dist/supabase-path-index.js +0 -138
- package/dist/supabase-published-store.d.ts +0 -56
- package/dist/supabase-published-store.js +0 -314
- package/dist/supabase-store.d.ts +0 -103
- package/dist/supabase-store.js +0 -678
- package/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/migrations/20261009120000_smoodly_v11.sql +0 -49
package/dist/site.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { schemaOf } from "./entry-store.js";
|
|
2
|
-
import { entrySegment } from "./paths.js";
|
|
3
1
|
import { lastPart } from "./addresses.js";
|
|
4
2
|
import { withDefaults } from "./field-defaults.js";
|
|
5
3
|
import { modelKey } from "./model-key.js";
|
|
6
|
-
import { documentStamp } from "./document-stamp.js";
|
|
7
4
|
import { SMOODLY_VERSION } from "./version.js";
|
|
8
|
-
import { toEntryItem, toPageItem } from "./read-shapes.js";
|
|
9
5
|
import { pageSeo } from "./seo.js";
|
|
10
|
-
import {
|
|
11
|
-
import { collectionOrder } from "./collections.js";
|
|
12
|
-
import { createProjector } from "./project.js";
|
|
6
|
+
import { pageFieldsOf, siteShape } from "./assemble.js";
|
|
13
7
|
/** The `page` prop for a loaded page. `href` is the app's seam from a
|
|
14
8
|
* locale-relative path to a site URL; the default keeps CMS paths as URLs. */
|
|
15
9
|
export function pageContextOf(hit, href = (p) => p) {
|
|
@@ -26,10 +20,11 @@ export function pageContextOf(hit, href = (p) => p) {
|
|
|
26
20
|
locales: Object.fromEntries(Object.entries(hit.locales).map(([code, alt]) => [code, alt && { ...alt, url: href(alt.path, code) }])),
|
|
27
21
|
};
|
|
28
22
|
}
|
|
29
|
-
|
|
23
|
+
/** The members of a site that need no store (the API client computes them from the same config, so a cache key or a
|
|
24
|
+
* code page never disagrees across the wire). */
|
|
25
|
+
export function siteStatics(options) {
|
|
30
26
|
const shape = siteShape(options);
|
|
31
27
|
const { locales, pageSchemas, homePageSlug, styles, custom, codeOwned } = shape;
|
|
32
|
-
const assembler = createAssembler(options, shape);
|
|
33
28
|
// Every cached read keys on it (spec 2026-09-30 tester findings §3): an upgrade or a model change starts a fresh cache.
|
|
34
29
|
const cacheKey = `${SMOODLY_VERSION}:${modelKey({
|
|
35
30
|
// Whole schemas: a collection's order, tree or title field changes a read as its fields do (functions drop out in the hash).
|
|
@@ -39,206 +34,9 @@ export function createSmoodlySite(options) {
|
|
|
39
34
|
elements: (options.elements ?? []).map((x) => ("schema" in x ? x.schema : x)),
|
|
40
35
|
pages: pageSchemas,
|
|
41
36
|
locales, homePageSlug, styles, custom,
|
|
37
|
+
// The project's image quality and cap are stamped on every image a read resolves.
|
|
38
|
+
images: options.images ?? {},
|
|
42
39
|
})}`;
|
|
43
|
-
// What a delivery row is stamped with (DESIGN §3, the document stamp): the document format and the schema parts
|
|
44
|
-
// that reach a document, never the package version, so a release leaves current documents current.
|
|
45
|
-
const stamp = documentStamp(options);
|
|
46
|
-
const localeOf = (o) => o?.locale ?? locales.default;
|
|
47
|
-
const published = () => options.stores().published;
|
|
48
|
-
const projector = createProjector({
|
|
49
|
-
assembler,
|
|
50
|
-
stores: () => { const s = options.stores(); return { pages: s.pages, entries: s.entries, published: s.published }; },
|
|
51
|
-
collections: options.collections, globals: options.globals ?? [], sections: options.sections, elements: options.elements,
|
|
52
|
-
locales, model: stamp,
|
|
53
|
-
});
|
|
54
|
-
// Delivery (spec 2026-10-07 delivery model §3–§4): outside draft mode a read takes the stored document when it is
|
|
55
|
-
// current — built by this model and not marked stale — and otherwise assembles that one document, stores it and serves it.
|
|
56
|
-
const current = (row) => row.model === stamp && !row.stale;
|
|
57
|
-
/** One writer of record (DESIGN §3, the document stamp): only the deployment whose stamp the space recorded rewrites
|
|
58
|
-
* rows; the first writer into an empty record records itself; every other deployment serves assembled documents
|
|
59
|
-
* and writes nothing. Read only when a heal is about to write, so a current page stays one call. A "no" is kept for
|
|
60
|
-
* the process (a later rebuild at this stamp is seen by the next process, which costs assembled reads, never wrong
|
|
61
|
-
* ones); a "yes" is read again on each heal, so a rebuild elsewhere stops this writer at its next heal. */
|
|
62
|
-
let yields = false;
|
|
63
|
-
async function mayWrite() {
|
|
64
|
-
if (yields)
|
|
65
|
-
return false;
|
|
66
|
-
// The record is derived like the rows: a read or write of it that fails is logged, and the page is served
|
|
67
|
-
// assembled with nothing written (spec 2026-10-07 §2 Failure), never failed on it.
|
|
68
|
-
try {
|
|
69
|
-
const recorded = await published().recordedModel();
|
|
70
|
-
if (recorded === null) {
|
|
71
|
-
await published().recordModel(stamp);
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
yields = recorded !== stamp;
|
|
75
|
-
return !yields;
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
console.error("smoodly: reading the space's recorded document stamp failed; served assembled, nothing written.", e);
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const fresh = (row) => current(row) && row.document != null;
|
|
83
|
-
const itemOf = (doc) => toEntryItem(doc.entry, doc.entry.fields, { path: doc.path, locale: doc.locale, locales: doc.locales, ...(doc.seo ? { seo: doc.seo } : {}) });
|
|
84
|
-
/** A page row's answer: its document when current (null for a folder or a hidden node), else the healed one. */
|
|
85
|
-
const pageOf = async (row, id, locale) => row && current(row) ? row.document : projector.healPage(id, locale, { seen: row !== null, write: await mayWrite() });
|
|
86
|
-
/** An entry document is served at an address only when its collection has addresses. */
|
|
87
|
-
const served = (doc) => (doc && doc.path !== null ? doc : null);
|
|
88
|
-
const entryOf = async (row, id, locale) => row && fresh(row) ? row.document : projector.healEntry(id, locale, { seen: row !== null, write: await mayWrite() });
|
|
89
|
-
/** An entry document as the projector would write it, from an assembled read (the address-less shape, path from the item). */
|
|
90
|
-
const assembledEntry = (collection, locale) => (read) => ({
|
|
91
|
-
kind: "entry", locale, collection, entry: { ...read.record, fields: read.item.fields },
|
|
92
|
-
path: read.item.path, locales: read.item.locales, ...(read.item.seo ? { seo: read.item.seo } : {}),
|
|
93
|
-
});
|
|
94
|
-
/** The published documents of a collection's query. Stale rows are rebuilt in one batched projection and the
|
|
95
|
-
* query read once more (spec §4): a rebuild that finds an entry unpublished removes its row, so it drops out. */
|
|
96
|
-
async function deliveredEntries(collection, o) {
|
|
97
|
-
const schema = schemaOf(options.collections, collection);
|
|
98
|
-
checkEntryQuery(schema, collection, o);
|
|
99
|
-
const locale = localeOf(o);
|
|
100
|
-
const read = () => published().entries(collection, {
|
|
101
|
-
locale, where: o.where, order: o.order ?? collectionOrder(schema), limit: o.limit, ids: o.ids,
|
|
102
|
-
...(o.parent !== undefined ? { parent: o.parent } : {}),
|
|
103
|
-
});
|
|
104
|
-
let rows = await read();
|
|
105
|
-
const behind = rows.filter((r) => !fresh(r)).map((r) => r.id);
|
|
106
|
-
if (behind.length > 0) {
|
|
107
|
-
// Not the writer of record: the list assembled now, nothing written (DESIGN §3).
|
|
108
|
-
if (!(await mayWrite()))
|
|
109
|
-
return (await assembler.entries(collection, { ...o, locale, draft: false })).map(assembledEntry(collection, locale));
|
|
110
|
-
// A failed rebuild never fails the read: the rows as read are served, and the next read tries again.
|
|
111
|
-
try {
|
|
112
|
-
await projector.entries(behind, { cap: Infinity, stamp: false });
|
|
113
|
-
rows = await read();
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
console.error(`smoodly: rebuilding ${behind.length} ${collection} documents in the delivery tables failed; served as read.`, e);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return rows.flatMap((r) => (r.document != null ? [r.document] : []));
|
|
120
|
-
}
|
|
121
|
-
const getEntries = async (collection, o = {}) => o.draft ? (await assembler.entries(collection, o)).map((r) => r.item) : (await deliveredEntries(collection, o)).map(itemOf);
|
|
122
|
-
/** Every entry of the collection in the locale, with the record a tree and a bare slug read `parentId` from. */
|
|
123
|
-
const readAll = async (collection, locale, draft) => draft
|
|
124
|
-
? assembler.entries(collection, { locale, draft })
|
|
125
|
-
: (await deliveredEntries(collection, { locale })).map((doc) => ({ record: doc.entry, item: itemOf(doc) }));
|
|
126
|
-
const api = {
|
|
127
|
-
async getPagesTree(o) {
|
|
128
|
-
const locale = localeOf(o);
|
|
129
|
-
if (o?.draft)
|
|
130
|
-
return assembler.tree(locale, { draft: true });
|
|
131
|
-
// One light read. A row behind the model or marked stale is rebuilt before answering (spec §4), and the tree read
|
|
132
|
-
// again; a rebuild that finds the node gone or unpublished removes its row.
|
|
133
|
-
let rows = await published().tree(locale);
|
|
134
|
-
const behind = rows.filter((r) => !current(r)).map((r) => r.id);
|
|
135
|
-
if (behind.length > 0) {
|
|
136
|
-
// Not the writer of record: the tree assembled now, nothing written (DESIGN §3).
|
|
137
|
-
if (!(await mayWrite()))
|
|
138
|
-
return assembler.tree(locale, { draft: false });
|
|
139
|
-
// One batched projection for every row behind (one tree read in the projector), then one more tree read.
|
|
140
|
-
// A failed rebuild never fails the read: the rows as read are served, and the next read tries again.
|
|
141
|
-
try {
|
|
142
|
-
await projector.pages(behind, { cap: Infinity, stamp: false });
|
|
143
|
-
rows = await published().tree(locale);
|
|
144
|
-
}
|
|
145
|
-
catch (e) {
|
|
146
|
-
console.error(`smoodly: rebuilding ${behind.length} page documents in the delivery tables failed; served as read.`, e);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// A node the tree hides (path null) leaves with its subtree, as does one whose parent has no row.
|
|
150
|
-
const build = (parentId) => rows.filter((r) => r.parentId === parentId && r.path !== null).map((r) => ({
|
|
151
|
-
id: r.id, title: r.title, path: r.path, hasPage: r.hasPage, fields: r.linkFields, children: build(r.id),
|
|
152
|
-
}));
|
|
153
|
-
return build(null);
|
|
154
|
-
},
|
|
155
|
-
async getPage(pathOrId, o) {
|
|
156
|
-
const locale = localeOf(o);
|
|
157
|
-
if (!o?.draft) {
|
|
158
|
-
// The content API stays strict (§7): a redirect row, and a code-owned page with no record, are not pages.
|
|
159
|
-
const hit = pathOrId.startsWith("/")
|
|
160
|
-
? (await delivered(locale, pathOrId, false)).hit
|
|
161
|
-
: await pageOf(await published().page(pathOrId, locale), pathOrId, locale);
|
|
162
|
-
return hit?.kind === "page" ? toPageItem(hit) : null;
|
|
163
|
-
}
|
|
164
|
-
const path = pathOrId.startsWith("/") ? pathOrId : (await options.stores().pages.pathsOf(pathOrId))[locale];
|
|
165
|
-
if (!path)
|
|
166
|
-
return null;
|
|
167
|
-
const hit = await site.resolve(locale, path, { draft: true });
|
|
168
|
-
return hit?.kind === "page" ? toPageItem(hit) : null;
|
|
169
|
-
},
|
|
170
|
-
getEntries,
|
|
171
|
-
async getEntry(collection, idOrPath, o) {
|
|
172
|
-
schemaOf(options.collections, collection); // an unknown collection is a wiring error on every branch
|
|
173
|
-
const locale = localeOf(o);
|
|
174
|
-
const draft = o?.draft ?? false;
|
|
175
|
-
if (idOrPath.startsWith("/")) {
|
|
176
|
-
const hit = draft ? await site.resolve(locale, idOrPath, { draft }) : (await delivered(locale, idOrPath, false)).hit;
|
|
177
|
-
return hit?.kind === "entry" && hit.collection === collection ? toEntryItem(hit.entry, hit.entry.fields, hit) : null;
|
|
178
|
-
}
|
|
179
|
-
if (draft) {
|
|
180
|
-
const [byId] = await assembler.entries(collection, { locale, draft, ids: [idOrPath] });
|
|
181
|
-
if (byId)
|
|
182
|
-
return byId.item;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const row = await published().entry(idOrPath, locale);
|
|
186
|
-
if (row && row.collection === collection && fresh(row))
|
|
187
|
-
return itemOf(row.document);
|
|
188
|
-
// A miss is a record no projection wrote yet, or a slug: one `get` tells them apart.
|
|
189
|
-
if (row?.collection === collection || (await options.stores().entries.get(collection, idOrPath))) {
|
|
190
|
-
const doc = await projector.healEntry(idOrPath, locale, { seen: row !== null, write: await mayWrite() });
|
|
191
|
-
if (doc)
|
|
192
|
-
return itemOf(doc);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// A bare slug names a ROOT entry only (sibling-scoped slugs, spec §4):
|
|
196
|
-
// match the localized slug directly (paths.ts owns slug derivation),
|
|
197
|
-
// regardless of whether the collection even has a `path`.
|
|
198
|
-
const all = await readAll(collection, locale, draft);
|
|
199
|
-
return all.find((r) => r.record.parentId === null && entrySegment(r.record, locale) === idOrPath)?.item ?? null;
|
|
200
|
-
},
|
|
201
|
-
async getEntriesTree(collection, o) {
|
|
202
|
-
const rows = await readAll(collection, localeOf(o), o?.draft ?? false);
|
|
203
|
-
const build = (parentId) => rows.filter((r) => r.record.parentId === parentId).map((r) => ({ ...r.item, children: build(r.record.id) }));
|
|
204
|
-
return build(null);
|
|
205
|
-
},
|
|
206
|
-
async getGlobal(item, o) {
|
|
207
|
-
const locale = localeOf(o);
|
|
208
|
-
if (o?.draft)
|
|
209
|
-
return assembler.global(item, locale, { draft: true });
|
|
210
|
-
// The item's row by its collection, in one read: a global is a one-record collection.
|
|
211
|
-
const [row] = await published().entries(item.name, { locale, limit: 1 });
|
|
212
|
-
return row && fresh(row) ? row.document : projector.healGlobal(item, locale, { seen: row !== undefined, write: await mayWrite() });
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
/** The published read of an address; `code`: the code-owned page answers where the index has no row. */
|
|
216
|
-
/** `globals`: the names of global items to bring along (schema v9); `rows` is what the route carried, when it had any. */
|
|
217
|
-
async function delivered(locale, path, code, globals) {
|
|
218
|
-
const route = await published().byAddress(locale, path, globals);
|
|
219
|
-
const rows = route?.globals;
|
|
220
|
-
if (route && fresh(route.row))
|
|
221
|
-
return { hit: route.row.document, rows };
|
|
222
|
-
let healed = null;
|
|
223
|
-
if (route) {
|
|
224
|
-
// Stale: rebuilt by id. Unpublished or moved since the row was written, the address is answered as a miss.
|
|
225
|
-
const write = await mayWrite();
|
|
226
|
-
healed = route.kind === "page" ? await projector.healPage(route.row.id, locale, { write }) : served(await projector.healEntry(route.row.id, locale, { write }));
|
|
227
|
-
if (healed && healed.path === path)
|
|
228
|
-
return { hit: healed, rows };
|
|
229
|
-
}
|
|
230
|
-
// A miss: the paths index doubles as the existence check — a record to assemble, a redirect row, or nothing.
|
|
231
|
-
// A record wins over the code-owned page at its address, as the renderer has it.
|
|
232
|
-
const hit = await site.locate(locale, path);
|
|
233
|
-
if (!hit)
|
|
234
|
-
return { hit: code ? codePage(locale, path) : null };
|
|
235
|
-
if (hit.redirect)
|
|
236
|
-
return { hit: { kind: "redirect", target: { kind: hit.kind, id: hit.id } } };
|
|
237
|
-
if (route && hit.id === route.row.id)
|
|
238
|
-
return { hit: healed, rows }; // just rebuilt
|
|
239
|
-
// By id: a page the tree hides has a current row the address lookup cannot reach; anything else heals there.
|
|
240
|
-
return { hit: await site.load(hit, locale, path), rows };
|
|
241
|
-
}
|
|
242
40
|
const codePath = (address) => (address === homePageSlug ? "/" : `/${address}`);
|
|
243
41
|
function codePage(locale, path) {
|
|
244
42
|
const spec = codeOwned.find((a) => a.template !== null && a.segments[locale] !== undefined && codePath(a.segments[locale]) === path);
|
|
@@ -262,50 +60,21 @@ export function createSmoodlySite(options) {
|
|
|
262
60
|
...(withSeo ? { seo: pageSeo(undefined, spec.title) } : {}),
|
|
263
61
|
};
|
|
264
62
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
return target.kind === "page"
|
|
281
|
-
? pageOf(await published().page(target.id, locale), target.id, locale)
|
|
282
|
-
: served(await entryOf(await published().entry(target.id, locale), target.id, locale));
|
|
283
|
-
},
|
|
284
|
-
lookup: async (locale, path) => (await delivered(locale, path, true)).hit,
|
|
285
|
-
async lookupWithGlobals(locale, path, items) {
|
|
286
|
-
const { hit, rows } = await delivered(locale, path, true, items.map((i) => i.name));
|
|
287
|
-
const globals = new Map();
|
|
288
|
-
if (rows) {
|
|
289
|
-
for (const item of items) {
|
|
290
|
-
const row = rows.find((r) => r.collection === item.name);
|
|
291
|
-
// No row: unpublished here, or never projected — left out rather than healed on every read, because a
|
|
292
|
-
// heal of an unpublished item writes nothing and the next read would heal again; the layout's own read
|
|
293
|
-
// pays the call where a page renders it. A stale row heals once and is written.
|
|
294
|
-
if (!row)
|
|
295
|
-
continue;
|
|
296
|
-
globals.set(item.name, fresh(row) ? row.document : await projector.healGlobal(item, locale, { seen: true, write: await mayWrite() }));
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return { hit, globals };
|
|
300
|
-
},
|
|
301
|
-
async resolve(locale, path, opts) {
|
|
302
|
-
// The content API stays strict (§7): a content address is not a redirect.
|
|
303
|
-
const target = await site.locate(locale, path);
|
|
304
|
-
return target && !target.redirect ? site.load(target, locale, path, opts) : null;
|
|
63
|
+
return { shape, locales, homePageSlug, styles, custom, cacheKey, codePage };
|
|
64
|
+
}
|
|
65
|
+
/** What createSmoodlySite takes, read off the config. */
|
|
66
|
+
export function siteOptions(config, stores) {
|
|
67
|
+
const { sections, elements, collections, globals, pages } = config.registry;
|
|
68
|
+
return {
|
|
69
|
+
// Absent for a site that reads over the API (api-client.ts): its statics need no store, and it never calls this.
|
|
70
|
+
stores: stores ?? (() => { throw new Error("smoodly: this site reads over the API — no store is bound."); }),
|
|
71
|
+
collections, globals,
|
|
72
|
+
// defineConfig's checks guarantee a section and an element carry `fields`.
|
|
73
|
+
sections: sections, elements: elements,
|
|
74
|
+
locales: config.locales, homePageSlug: config.homePageSlug, styles: config.styles, custom: config.custom, pages,
|
|
75
|
+
images: {
|
|
76
|
+
...(config.media.quality !== undefined ? { quality: config.media.quality } : {}),
|
|
77
|
+
...(config.media.maxWidth !== undefined ? { maxWidth: config.media.maxWidth } : {}),
|
|
305
78
|
},
|
|
306
|
-
codePage,
|
|
307
|
-
projector,
|
|
308
|
-
...api,
|
|
309
79
|
};
|
|
310
|
-
return site;
|
|
311
80
|
}
|
package/dist/space-transfer.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
/** The tables a space's content lives in, the ones export and import carry (members are listed, never copied). */
|
|
2
|
+
export declare const CONTENT_TABLES: readonly ["pages", "page_locales", "page_versions", "entries", "entry_locales", "entry_versions", "refs", "paths", "assets"];
|
|
3
|
+
export type ContentTable = (typeof CONTENT_TABLES)[number];
|
|
2
4
|
/** The folder layout's own version, apart from the schema (spec §7). */
|
|
3
5
|
export declare const FORMAT = 1;
|
|
4
6
|
export type Row = Record<string, unknown>;
|
|
@@ -56,5 +58,6 @@ export declare function describeExport(rows: TableRows, bytes: number): string;
|
|
|
56
58
|
export declare function describeImport(rows: TableRows): string;
|
|
57
59
|
/** The target check's refusal (spec §9.1), naming only the tables that hold foreign rows. */
|
|
58
60
|
export declare function notEmptyMessage(foreign: Record<ContentTable, number>): string;
|
|
59
|
-
/** Spec §6: the target
|
|
61
|
+
/** Spec §6: the target space's schema version must equal the export's. A space's schema is its object's step list,
|
|
62
|
+
* brought forward by its service (spec 2026-10-15 the space object), so the fix is the other side's service. */
|
|
60
63
|
export declare function versionMismatchMessage(target: number, exported: number): string;
|
package/dist/space-transfer.js
CHANGED
|
@@ -5,7 +5,18 @@
|
|
|
5
5
|
// mirror of the target's foreign keys and the import's "only this
|
|
6
6
|
// export's rows" test. No I/O; the CLI halves are cli/export.ts and
|
|
7
7
|
// cli/import.ts.
|
|
8
|
-
import
|
|
8
|
+
/** The tables a space's content lives in, the ones export and import carry (members are listed, never copied). */
|
|
9
|
+
export const CONTENT_TABLES = [
|
|
10
|
+
"pages",
|
|
11
|
+
"page_locales",
|
|
12
|
+
"page_versions",
|
|
13
|
+
"entries",
|
|
14
|
+
"entry_locales",
|
|
15
|
+
"entry_versions",
|
|
16
|
+
"refs",
|
|
17
|
+
"paths",
|
|
18
|
+
"assets",
|
|
19
|
+
];
|
|
9
20
|
/** The folder layout's own version, apart from the schema (spec §7). */
|
|
10
21
|
export const FORMAT = 1;
|
|
11
22
|
/** Every foreign key's target lands before the row that names it (spec §9.3). */
|
|
@@ -171,11 +182,12 @@ const NOUN = {
|
|
|
171
182
|
/** The target check's refusal (spec §9.1), naming only the tables that hold foreign rows. */
|
|
172
183
|
export function notEmptyMessage(foreign) {
|
|
173
184
|
const parts = INSERT_ORDER.filter((t) => foreign[t] > 0).map((t) => plural(foreign[t], ...NOUN[t]));
|
|
174
|
-
return `The target space holds ${listJoin(parts)} that this export does not. Import needs an empty space:
|
|
185
|
+
return `The target space holds ${listJoin(parts)} that this export does not. Import needs an empty space: import into a new one.`;
|
|
175
186
|
}
|
|
176
|
-
/** Spec §6: the target
|
|
187
|
+
/** Spec §6: the target space's schema version must equal the export's. A space's schema is its object's step list,
|
|
188
|
+
* brought forward by its service (spec 2026-10-15 the space object), so the fix is the other side's service. */
|
|
177
189
|
export function versionMismatchMessage(target, exported) {
|
|
178
190
|
return target < exported
|
|
179
|
-
? `The target
|
|
180
|
-
: `The target
|
|
191
|
+
? `The target space is at schema v${target} and this export is v${exported}: a newer service wrote it. Import it into a space whose service has reached v${exported}.`
|
|
192
|
+
: `The target space is at schema v${target}, newer than this export (v${exported}): export the source again once its service has reached v${target}.`;
|
|
181
193
|
}
|
package/dist/store.d.ts
CHANGED
|
@@ -4,9 +4,8 @@ import type { Addresses } from "./addresses.ts";
|
|
|
4
4
|
import type { Anchors } from "./anchors.ts";
|
|
5
5
|
import { type RefEdge, type SectionLike } from "./refs.ts";
|
|
6
6
|
import type { Toolset } from "./toolset.ts";
|
|
7
|
-
import type {
|
|
8
|
-
import
|
|
9
|
-
import { type DraftToken } from "./draft-token.ts";
|
|
7
|
+
import type { PathHit, PathIndex, PathRow } from "./path-index.ts";
|
|
8
|
+
import type { DraftToken } from "./draft-token.ts";
|
|
10
9
|
import { type LocaleSet, type PagePathOptions, type PathTarget } from "./paths.ts";
|
|
11
10
|
export type PageLocale = {
|
|
12
11
|
/** The node's OWN segment in this locale; the URL is derived (paths.ts). */
|
|
@@ -71,8 +70,6 @@ export interface PageStore {
|
|
|
71
70
|
/** Every page with each locale's draft and live tree, in few reads (spec 2026-09-27 editor safety §3). */
|
|
72
71
|
scanTrees(): Promise<PageScan[]>;
|
|
73
72
|
resolvePath(locale: string, path: string): Promise<PathHit | null>;
|
|
74
|
-
/** The database's schema version from `smoodly_meta`; null when the table is missing (older than v1). Memory: always current (spec 2026-09-26 §5). */
|
|
75
|
-
schemaVersion(): Promise<number | null>;
|
|
76
73
|
/** locale → path, for the locales the page exists in. */
|
|
77
74
|
pathsOf(id: string): Promise<Record<string, string>>;
|
|
78
75
|
/** locale → the target's live path, for any page or entry — the renderer's redirect branch (spec 2026-09-26 redirects §7). */
|
|
@@ -187,7 +184,6 @@ export declare const pageErrors: {
|
|
|
187
184
|
fixedTaken: (key: string) => Error;
|
|
188
185
|
fixedAtRoot: () => Error;
|
|
189
186
|
};
|
|
190
|
-
export { assertSupportedLocale } from "./paths.ts";
|
|
191
187
|
/** The page's row for a locale — the "does not exist in locale" guard every per-locale write runs. */
|
|
192
188
|
export declare function localeRow(page: PageRecord, locale: string): PageLocale;
|
|
193
189
|
/** A page's refs rows cover EVERY live tree — each locale's draft and
|
|
@@ -199,77 +195,3 @@ export declare function pageRefEdges(trees: PageTree[], sections: SectionLike[],
|
|
|
199
195
|
* THERE — every referenced entry must exist, have a row in that locale,
|
|
200
196
|
* and be published in it. Throws naming the offenders. */
|
|
201
197
|
export declare function assertPublishable(label: string, tree: PageTree, sections: SectionLike[], entries: EntryStore, locale: string): Promise<void>;
|
|
202
|
-
export declare class MemoryPageStore implements PageStore {
|
|
203
|
-
private options;
|
|
204
|
-
private pages;
|
|
205
|
-
private versions;
|
|
206
|
-
/** Append-only creation-order tie-break; `size + 1` stays fresh only because entries are never deleted. */
|
|
207
|
-
private seq;
|
|
208
|
-
private paths;
|
|
209
|
-
private opts;
|
|
210
|
-
constructor(options?: PageStoreOptions & {
|
|
211
|
-
refs?: MemoryRefIndex;
|
|
212
|
-
});
|
|
213
|
-
private byId;
|
|
214
|
-
private must;
|
|
215
|
-
private isHome;
|
|
216
|
-
private snapshot;
|
|
217
|
-
private children;
|
|
218
|
-
private hasChildren;
|
|
219
|
-
private siblings;
|
|
220
|
-
/** Sibling slugs are unique per parent PER LOCALE — a friendlier error
|
|
221
|
-
* than the path index's, which stays the real invariant. Code-owned
|
|
222
|
-
* roots are left out: their path is their address, and the path index
|
|
223
|
-
* guards it (spec 2026-09-29 nested paths §3). */
|
|
224
|
-
private assertSlugFree;
|
|
225
|
-
private subtree;
|
|
226
|
-
private writePaths;
|
|
227
|
-
/** Every locale's draft and published trees, deduped by version id. */
|
|
228
|
-
private liveTrees;
|
|
229
|
-
private writeRefs;
|
|
230
|
-
private newVersion;
|
|
231
|
-
/** The draft rule (spec 2026-09-13 history §1): the unpublished draft row is
|
|
232
|
-
* rewritten in place; a new row starts only when the draft IS the published one. */
|
|
233
|
-
private writeDraftVersion;
|
|
234
|
-
private dropVersions;
|
|
235
|
-
createPage(input: CreatePageInput, by?: string): Promise<PageRecord>;
|
|
236
|
-
getPage(id: string): Promise<PageRecord | null>;
|
|
237
|
-
scanTrees(): Promise<PageScan[]>;
|
|
238
|
-
listPages(): Promise<PageRecord[]>;
|
|
239
|
-
resolvePath(locale: string, path: string): Promise<PathHit | null>;
|
|
240
|
-
schemaVersion(): Promise<number>;
|
|
241
|
-
pathsOf(id: string): Promise<Record<string, string>>;
|
|
242
|
-
targetPaths(target: PathTarget): Promise<Record<string, string>>;
|
|
243
|
-
redirectsOf(id: string): Promise<Record<string, string[]>>;
|
|
244
|
-
listLive(locale: string): Promise<PathRow[]>;
|
|
245
|
-
listRedirects(locale: string): Promise<PathRow[]>;
|
|
246
|
-
addRedirect(row: PathRow): Promise<void>;
|
|
247
|
-
removeRedirect(locale: string, path: string): Promise<void>;
|
|
248
|
-
addLocale(id: string, locale: string, options: AddLocaleOptions, by?: string): Promise<PageRecord>;
|
|
249
|
-
removeLocale(id: string, locale: string): Promise<PageRecord>;
|
|
250
|
-
rename(id: string, patch: {
|
|
251
|
-
locale: string;
|
|
252
|
-
slug?: string;
|
|
253
|
-
title?: string;
|
|
254
|
-
}, by?: string, options?: {
|
|
255
|
-
redirect?: boolean;
|
|
256
|
-
}): Promise<PageRecord>;
|
|
257
|
-
move(id: string, to: {
|
|
258
|
-
parentId: string | null;
|
|
259
|
-
index?: number;
|
|
260
|
-
}, options?: {
|
|
261
|
-
redirect?: boolean;
|
|
262
|
-
}): Promise<PageRecord>;
|
|
263
|
-
setTemplate(id: string, template: string, by?: string): Promise<PageRecord>;
|
|
264
|
-
saveDraft(id: string, locale: string, tree: PageTree, by?: string, expected?: DraftToken | null): Promise<PageVersion>;
|
|
265
|
-
publish(id: string, locale: string, by?: string, expected?: DraftToken | null): Promise<PageRecord>;
|
|
266
|
-
discardDraft(id: string, locale: string, by?: string, expected?: DraftToken | null): Promise<PageRecord>;
|
|
267
|
-
unpublish(id: string, locale: string, by?: string): Promise<PageRecord>;
|
|
268
|
-
restore(id: string, locale: string, versionId: string, by?: string, expected?: DraftToken | null): Promise<PageRecord>;
|
|
269
|
-
getVersion(versionId: string): Promise<PageVersion | null>;
|
|
270
|
-
getDraftTree(id: string, locale: string): Promise<PageTree | null>;
|
|
271
|
-
fieldsOf(versionIds: string[]): Promise<Record<string, Record<string, unknown>>>;
|
|
272
|
-
getPublishedTree(id: string, locale: string): Promise<PageTree | null>;
|
|
273
|
-
listVersions(id: string, locale: string): Promise<PageVersion[]>;
|
|
274
|
-
deletePage(id: string): Promise<void>;
|
|
275
|
-
}
|