smoodly 0.0.44 → 0.0.46
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 +3 -3
- package/dist/addresses.d.ts +6 -7
- package/dist/addresses.js +6 -4
- package/dist/admin/PublicSans-LICENSE.txt +93 -0
- package/dist/admin/auth-config.d.ts +6 -0
- package/dist/admin/auth-config.js +1 -0
- package/dist/admin/browser/fetch-op.d.ts +10 -0
- package/dist/admin/browser/fetch-op.js +44 -0
- package/dist/admin/browser/main.d.ts +1 -0
- package/dist/admin/browser/main.js +21 -0
- package/dist/admin/custom-section.d.ts +1 -1
- package/dist/admin/drift.d.ts +1 -1
- package/dist/admin/editor/EditorView.js +14 -14
- package/dist/admin/editor/autosave.js +1 -1
- package/dist/admin/editor/canvas-address.d.ts +2 -0
- package/dist/admin/editor/canvas-address.js +8 -0
- package/dist/admin/editor/overlay-geometry.d.ts +1 -1
- package/dist/admin/editor/protocol.d.ts +18 -4
- package/dist/admin/editor/protocol.js +15 -1
- package/dist/admin/editor/useCanvasBridge.d.ts +1 -1
- package/dist/admin/editor/useCanvasBridge.js +5 -6
- package/dist/admin/editor/useInlineSession.js +4 -2
- package/dist/admin/files.d.ts +11 -0
- package/dist/admin/files.generated.js +2 -0
- package/dist/admin/files.js +8 -0
- package/dist/admin/forms/useRefOptions.d.ts +2 -2
- package/dist/admin/forms/useRefOptions.js +2 -2
- package/dist/admin/forms/useRichtextEditor.js +4 -4
- package/dist/admin/handler.d.ts +40 -0
- package/dist/admin/handler.js +151 -0
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/ops-impl.d.ts +7 -1
- package/dist/admin/ops-impl.js +110 -33
- package/dist/admin/ops.d.ts +3 -2
- package/dist/admin/page-data.d.ts +21 -0
- package/dist/admin/page-data.js +4 -0
- package/dist/admin/page.d.ts +6 -0
- package/dist/admin/page.js +35 -0
- package/dist/admin/serialize.js +7 -7
- package/dist/admin/shell/AdminApp.js +2 -2
- package/dist/admin/shell/EntryForm.js +1 -1
- package/dist/admin/shell/router.d.ts +3 -16
- package/dist/admin/shell/router.js +3 -39
- package/dist/admin/shell/session.d.ts +2 -5
- package/dist/admin/supabase-auth.js +1 -1
- package/dist/admin/tree-ops.d.ts +1 -1
- package/dist/admin/ui/fonts.d.ts +12 -0
- package/dist/admin/ui/fonts.js +22 -0
- package/dist/admin/ui/theme.d.ts +13 -4
- package/dist/admin/ui/theme.js +28 -157
- package/dist/assemble.d.ts +60 -0
- package/dist/assemble.js +359 -0
- package/dist/astro/cache.d.ts +34 -0
- package/dist/astro/cache.js +69 -0
- package/dist/astro/components/Custom.astro +21 -0
- package/dist/astro/components/Guarded.astro +24 -0
- package/dist/astro/components/Head.astro +26 -0
- package/dist/astro/components/Image.astro +20 -0
- package/dist/astro/components/RichText.astro +23 -0
- package/dist/astro/components/Section.astro +20 -0
- package/dist/astro/components/Video.astro +5 -0
- package/dist/astro/components/Zone.astro +19 -0
- package/dist/astro/components.d.ts +6 -0
- package/dist/astro/components.js +7 -0
- package/dist/astro/content-loaders.d.ts +40 -0
- package/dist/astro/content-loaders.js +100 -0
- package/dist/astro/content.d.ts +9 -0
- package/dist/astro/content.js +17 -0
- package/dist/astro/custom.d.ts +1 -0
- package/dist/astro/custom.js +3 -0
- package/dist/astro/defaults.d.ts +20 -0
- package/dist/astro/defaults.js +21 -0
- package/dist/astro/env-files.d.ts +1 -0
- package/dist/astro/env-files.js +21 -0
- package/dist/astro/head.d.ts +13 -0
- package/dist/astro/head.js +19 -0
- package/dist/astro/image.d.ts +27 -0
- package/dist/astro/image.js +29 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +111 -0
- package/dist/astro/locale-paths.d.ts +30 -0
- package/dist/astro/locale-paths.js +38 -0
- package/dist/astro/middleware.d.ts +1 -0
- package/dist/astro/middleware.js +83 -0
- package/dist/astro/preview-document.d.ts +1 -0
- package/dist/astro/preview-document.js +18 -0
- package/dist/astro/preview.d.ts +10 -0
- package/dist/astro/preview.js +72 -0
- package/dist/astro/richtext.d.ts +15 -0
- package/dist/astro/richtext.js +78 -0
- package/dist/astro/routes/admin.d.ts +3 -0
- package/dist/astro/routes/admin.js +41 -0
- package/dist/astro/routes/page.astro +17 -0
- package/dist/astro/routes/preview.astro +11 -0
- package/dist/astro/runtime.d.ts +32 -0
- package/dist/astro/runtime.js +46 -0
- package/dist/astro/scope.d.ts +45 -0
- package/dist/astro/scope.js +103 -0
- package/dist/astro/server.d.ts +14 -0
- package/dist/astro/server.js +45 -0
- package/dist/astro/shell.d.ts +67 -0
- package/dist/astro/shell.js +63 -0
- package/dist/astro/style.d.ts +1 -0
- package/dist/astro/style.js +11 -0
- package/dist/astro/views-check.d.ts +3 -0
- package/dist/astro/views-check.js +19 -0
- package/dist/astro/zone.d.ts +39 -0
- package/dist/astro/zone.js +33 -0
- package/dist/bind.d.ts +19 -0
- package/dist/bind.js +34 -0
- package/dist/bridge/bridge.d.ts +9 -0
- package/dist/bridge/bridge.js +107 -0
- package/dist/bridge/entry.d.ts +1 -0
- package/dist/bridge/entry.js +5 -0
- package/dist/bridge/preview-pill.d.ts +18 -0
- package/dist/bridge/preview-pill.js +115 -0
- package/dist/cli/import.d.ts +3 -0
- package/dist/cli/import.js +10 -1
- package/dist/cli/load-collections.js +4 -3
- package/dist/cli/load-config.d.ts +4 -0
- package/dist/cli/load-config.js +26 -0
- package/dist/cli/main.d.ts +7 -1
- package/dist/cli/main.js +32 -7
- package/dist/cli/migrate.d.ts +3 -0
- package/dist/cli/migrate.js +8 -1
- package/dist/cli/rebuild.d.ts +23 -0
- package/dist/cli/rebuild.js +22 -0
- package/dist/cli/space-io.d.ts +15 -1
- package/dist/cli/space-io.js +14 -0
- package/dist/client.js +5 -0
- package/dist/config.d.ts +28 -20
- package/dist/config.js +71 -75
- package/dist/content/content-api.d.ts +1 -1
- package/dist/content/node.d.ts +2 -3
- package/dist/content/node.js +2 -1
- package/dist/crash-message.d.ts +1 -0
- package/dist/crash-message.js +8 -0
- package/dist/css.d.ts +4 -0
- package/dist/css.js +5 -2
- package/dist/custom/CustomSection.d.ts +1 -1
- package/dist/custom/CustomSection.js +4 -8
- package/dist/custom/css.js +2 -2
- package/dist/custom/index.d.ts +6 -10
- package/dist/custom/index.js +1 -2
- package/dist/custom/layout.d.ts +5 -0
- package/dist/custom/layout.js +14 -0
- package/dist/entry-page.d.ts +3 -18
- package/dist/env.js +1 -1
- package/dist/fields.js +2 -1
- package/dist/global-id.js +2 -2
- package/dist/global.d.ts +5 -2
- package/dist/global.js +8 -5
- package/dist/image/next-config.d.ts +3 -0
- package/dist/image/next-config.js +12 -7
- package/dist/image/remote-pattern.d.ts +1 -0
- package/dist/image/remote-pattern.js +5 -0
- package/dist/index.d.ts +15 -17
- package/dist/index.js +10 -19
- package/dist/marks.d.ts +7 -0
- package/dist/marks.js +7 -0
- package/dist/next/admin-route.d.ts +7 -0
- package/dist/next/admin-route.js +28 -0
- package/dist/next/bridge.d.ts +1 -0
- package/dist/next/bridge.js +26 -0
- package/dist/next/canvas-boundary.d.ts +1 -1
- package/dist/next/canvas-slot.d.ts +1 -0
- package/dist/next/canvas-slot.js +4 -3
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +1 -0
- package/dist/next/page-renderer.d.ts +5 -1
- package/dist/next/page-renderer.js +19 -33
- package/dist/next/preview-pill-define.d.ts +1 -0
- package/dist/next/preview-pill-define.js +10 -0
- package/dist/next/preview-pill.d.ts +0 -3
- package/dist/next/preview-pill.js +7 -50
- package/dist/next/queries.d.ts +3 -28
- package/dist/next/queries.js +3 -90
- package/dist/next/reads.d.ts +2 -0
- package/dist/next/reads.js +18 -0
- package/dist/page-context.d.ts +89 -0
- package/dist/page-context.js +8 -0
- package/dist/pairing.d.ts +2 -22
- package/dist/project.d.ts +69 -0
- package/dist/project.js +210 -0
- package/dist/published.d.ts +96 -0
- package/dist/published.js +125 -0
- package/dist/react.d.ts +61 -0
- package/dist/react.js +17 -0
- package/dist/read-shapes.d.ts +1 -1
- package/dist/reads.d.ts +80 -0
- package/dist/reads.js +142 -0
- package/dist/refs.d.ts +1 -1
- package/dist/registration.d.ts +8 -0
- package/dist/registration.js +17 -0
- package/dist/render.d.ts +5 -83
- package/dist/render.js +5 -10
- package/dist/resolve.d.ts +1 -1
- package/dist/richtext/index.d.ts +6 -2
- package/dist/richtext/index.js +3 -2
- package/dist/richtext-href.d.ts +4 -0
- package/dist/richtext-href.js +17 -0
- package/dist/richtext-render.js +1 -14
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +1 -1
- package/dist/section-crash.d.ts +2 -2
- package/dist/section-crash.js +2 -7
- package/dist/section-wrapper.d.ts +3 -10
- package/dist/section-wrapper.js +3 -2
- package/dist/site.d.ts +38 -14
- package/dist/site.js +159 -286
- package/dist/smd-key.d.ts +9 -0
- package/dist/smd-key.js +1 -0
- package/dist/sql-space.d.ts +4 -0
- package/dist/sql-space.js +10 -5
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +117 -0
- package/dist/store.d.ts +1 -1
- package/dist/supabase-published-store.d.ts +35 -0
- package/dist/supabase-published-store.js +215 -0
- package/dist/supabase-store.d.ts +1 -1
- package/dist/toolset.d.ts +2 -4
- package/dist/toolset.js +6 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/view-props.d.ts +40 -0
- package/dist/view-props.js +1 -0
- package/dist/views.d.ts +36 -0
- package/dist/views.js +52 -0
- package/dist/zones.d.ts +7 -7
- package/dist/zones.js +7 -6
- package/migrations/20261007120000_smoodly_v8.sql +154 -0
- package/migrations/20261007200000_smoodly_v9.sql +30 -0
- package/package.json +46 -19
- package/dist/admin/next/bridge.d.ts +0 -2
- package/dist/admin/next/bridge.js +0 -129
- package/dist/admin/next/create-admin.d.ts +0 -19
- package/dist/admin/next/create-admin.js +0 -44
- package/dist/admin/next/index.d.ts +0 -8
- package/dist/admin/next/index.js +0 -8
- package/dist/admin/next/op-handler.d.ts +0 -18
- package/dist/admin/next/op-handler.js +0 -31
package/dist/next/queries.js
CHANGED
|
@@ -1,92 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// and cached under a tag outside it — the pattern getSmoodlyGlobal set.
|
|
4
|
-
// Lives in next/ because draftMode and unstable_cache are Next's; an
|
|
5
|
-
// Astro glue wraps the same site reads over its own cache seam.
|
|
6
|
-
import { unstable_cache } from "next/cache";
|
|
7
|
-
import { draftMode } from "next/headers";
|
|
8
|
-
import { connection } from "next/server";
|
|
9
|
-
import { collectionTag, entryTag, pageTag, pagesTreeTag, globalTag } from "../revalidate.js";
|
|
10
|
-
import { referencedCollections } from "../read-shapes.js";
|
|
11
|
-
/** JSON with object keys sorted at every level and `undefined` dropped, so two spellings of one query share a unit. */
|
|
12
|
-
function stable(value) {
|
|
13
|
-
if (Array.isArray(value))
|
|
14
|
-
return value.map(stable);
|
|
15
|
-
if (value !== null && typeof value === "object") {
|
|
16
|
-
return Object.fromEntries(Object.entries(value)
|
|
17
|
-
.filter(([, v]) => v !== undefined)
|
|
18
|
-
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
19
|
-
.map(([k, v]) => [k, stable(v)]));
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
/** A stable key for an entries query: only what was given, normalized. */
|
|
24
|
-
function entriesKey(o) {
|
|
25
|
-
const { locale: _locale, ...rest } = o;
|
|
26
|
-
return JSON.stringify(stable(rest));
|
|
27
|
-
}
|
|
1
|
+
import { createQueries } from "../reads.js";
|
|
2
|
+
import { nextReads } from "./reads.js";
|
|
28
3
|
export function createSmoodlyQueries(options) {
|
|
29
|
-
|
|
30
|
-
const collectionByName = new Map(options.collections.map((c) => [c.name, c]));
|
|
31
|
-
const globalByName = new Map((options.globals ?? []).map((s) => [s.name, s]));
|
|
32
|
-
const localeOf = (o) => o?.locale ?? site.locales.default;
|
|
33
|
-
/** Every query gates on the request (never a build prerender), then reads live in draft mode or cached under `tags`. */
|
|
34
|
-
async function run(key, tags, read) {
|
|
35
|
-
await connection();
|
|
36
|
-
const { isEnabled: draft } = await draftMode();
|
|
37
|
-
if (draft)
|
|
38
|
-
return read(true);
|
|
39
|
-
return unstable_cache(() => read(false), ["smoodly-q", site.cacheKey, ...key], { tags })();
|
|
40
|
-
}
|
|
41
|
-
/** The tag needs the id: a path is located first, uncached, as the renderer does. */
|
|
42
|
-
async function idOf(kind, locale, pathOrId) {
|
|
43
|
-
if (!pathOrId.startsWith("/"))
|
|
44
|
-
return pathOrId;
|
|
45
|
-
await connection();
|
|
46
|
-
const target = await site.locate(locale, pathOrId);
|
|
47
|
-
return target?.kind === kind ? target.id : null;
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
getPageTree: (o) => {
|
|
51
|
-
const locale = localeOf(o);
|
|
52
|
-
return run(["pageTree", locale], [pagesTreeTag()], (draft) => site.getPagesTree({ locale, draft }));
|
|
53
|
-
},
|
|
54
|
-
async getPage(pathOrId, o) {
|
|
55
|
-
const locale = localeOf(o);
|
|
56
|
-
const id = await idOf("page", locale, pathOrId);
|
|
57
|
-
if (!id)
|
|
58
|
-
return null;
|
|
59
|
-
return run(["page", locale, pathOrId], [pageTag(id)], (draft) => site.getPage(pathOrId, { locale, draft }));
|
|
60
|
-
},
|
|
61
|
-
getEntries: (collection, o = {}) => {
|
|
62
|
-
const locale = localeOf(o);
|
|
63
|
-
const { locale: _locale, ...query } = o;
|
|
64
|
-
// The unit also depends on every collection its references resolve into (spec 2026-09-29 content API contract §6).
|
|
65
|
-
const schema = collectionByName.get(collection);
|
|
66
|
-
const tags = [collection, ...(schema ? referencedCollections(schema, [...options.collections]) : [])];
|
|
67
|
-
return run(["entries", collection, locale, entriesKey(o)], [...new Set(tags)].map(collectionTag), (draft) => site.getEntries(collection, { ...query, locale, draft }));
|
|
68
|
-
},
|
|
69
|
-
async getEntry(collection, idOrPath, o) {
|
|
70
|
-
const locale = localeOf(o);
|
|
71
|
-
const id = await idOf("entry", locale, idOrPath);
|
|
72
|
-
if (!id)
|
|
73
|
-
return null;
|
|
74
|
-
return run(["entry", collection, locale, idOrPath], [entryTag(id)], async (draft) => {
|
|
75
|
-
const hit = await site.getEntry(collection, idOrPath, { locale, draft });
|
|
76
|
-
// The site layer also answers a bare root slug, but the unit would sit under
|
|
77
|
-
// a tag no op ever fires — so a slug is refused here rather than cached stale.
|
|
78
|
-
if (hit && !idOrPath.startsWith("/") && hit.id !== idOrPath) {
|
|
79
|
-
throw new Error(`smoodly: getEntry("${collection}", "${idOrPath}") takes an id or a path — for a slug, pass the entry's path.`);
|
|
80
|
-
}
|
|
81
|
-
return hit;
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
async getGlobal(name, o) {
|
|
85
|
-
const locale = localeOf(o);
|
|
86
|
-
const item = globalByName.get(name);
|
|
87
|
-
if (!item)
|
|
88
|
-
throw new Error(`smoodly: no global item named "${name}".`);
|
|
89
|
-
return run(["global", name, locale], [globalTag(name)], (draft) => site.getGlobal(item, { locale, draft }));
|
|
90
|
-
},
|
|
91
|
-
};
|
|
4
|
+
return createQueries(options, nextReads);
|
|
92
5
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The read layer's adapter for Next (spec 2026-10-02 astro cloud fit §6): the one place in the Next glue
|
|
2
|
+
// that knows where a read is cached. The gate is `connection()`, so a build prerenders nothing against
|
|
3
|
+
// the database; inside draft mode a unit reads the live draft and touches no cache; outside it the
|
|
4
|
+
// published read is one `unstable_cache` unit under the unit's key and tags, which the admin's
|
|
5
|
+
// operations expire (revalidate.ts). Next's cache key is the callback's source text plus the key parts,
|
|
6
|
+
// so every unit is cached from the one closure below (a unit's `tagsOf` is ignored: the tags go in before the read): a second call site would split a shared unit.
|
|
7
|
+
import { unstable_cache } from "next/cache";
|
|
8
|
+
import { draftMode } from "next/headers";
|
|
9
|
+
import { connection } from "next/server";
|
|
10
|
+
export const nextReads = {
|
|
11
|
+
gate: () => connection(),
|
|
12
|
+
async run(unit) {
|
|
13
|
+
const { isEnabled: draft } = await draftMode();
|
|
14
|
+
if (draft)
|
|
15
|
+
return unit.read(true);
|
|
16
|
+
return unstable_cache(() => unit.read(false), unit.key, { tags: unit.tags })();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { PageAlternate } from "./paths.ts";
|
|
2
|
+
import type { StyleValues } from "./styles.ts";
|
|
3
|
+
export type TreeNode = {
|
|
4
|
+
id: string;
|
|
5
|
+
type: string;
|
|
6
|
+
fields: Record<string, unknown>;
|
|
7
|
+
/** The developer's knobs (spec 2026-09-14 §3). */
|
|
8
|
+
settings?: Record<string, unknown>;
|
|
9
|
+
/** Smoodly's styles steps (spec 2026-09-14 §2); absent keys are Default. */
|
|
10
|
+
styles?: Record<string, unknown>;
|
|
11
|
+
/** The custom-CSS snippet (spec 2026-09-14 §4), scoped by the wrapper. */
|
|
12
|
+
css?: string;
|
|
13
|
+
};
|
|
14
|
+
/** `meta` is the page's own metadata (SEO title, description, og image).
|
|
15
|
+
* It rides inside the tree deliberately: trees are already JSONB in
|
|
16
|
+
* page_versions, so this needs no DDL change, and page meta becomes
|
|
17
|
+
* versioned with content — draft SEO edits stay unpublished, and publish
|
|
18
|
+
* moves content and meta together. */
|
|
19
|
+
export type PageTree = {
|
|
20
|
+
zones: Record<string, TreeNode[]>;
|
|
21
|
+
meta?: Record<string, unknown>;
|
|
22
|
+
fields?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
/** Where a rendered page sits in the tree — what a view needs for
|
|
25
|
+
* breadcrumbs and sub-navigation without a query (spec 2026-09-05 §5).
|
|
26
|
+
* `path` is the locale-relative CMS path (what the content API returns);
|
|
27
|
+
* `url` is that path through the app's `href` seam — `/fi/yritys` under
|
|
28
|
+
* next-intl, `/yritys` without it — so a page shape links the same way
|
|
29
|
+
* in both layouts. `fields` is the linked page's own page fields (spec
|
|
30
|
+
* 2026-09-10 page fields §3): the published tree's outside draft mode,
|
|
31
|
+
* raw (no asset resolution), defaults applied, `{}` for a folder. */
|
|
32
|
+
export type PageLink = {
|
|
33
|
+
title: string;
|
|
34
|
+
path: string;
|
|
35
|
+
hasPage: boolean;
|
|
36
|
+
fields: Record<string, unknown>;
|
|
37
|
+
};
|
|
38
|
+
export type PageContextLink = PageLink & {
|
|
39
|
+
url: string;
|
|
40
|
+
};
|
|
41
|
+
/** The page's address in one other locale, `url` through the app's href seam (spec 2026-09-15 §3). */
|
|
42
|
+
export type PageContextAlternate = PageAlternate & {
|
|
43
|
+
url: string;
|
|
44
|
+
};
|
|
45
|
+
export type PageContext<Fields = Record<string, unknown>> = {
|
|
46
|
+
id: string;
|
|
47
|
+
title: string;
|
|
48
|
+
path: string;
|
|
49
|
+
url: string;
|
|
50
|
+
/** The locale the page was rendered in. */
|
|
51
|
+
locale: string;
|
|
52
|
+
/** The page's own fields, typed from its schema on the view; resolved, defaults applied. */
|
|
53
|
+
fields: Fields;
|
|
54
|
+
/** Root first, parent last. */
|
|
55
|
+
ancestors: ReadonlyArray<PageContextLink>;
|
|
56
|
+
children: ReadonlyArray<PageContextLink>;
|
|
57
|
+
/** One key per supported locale: the page's `{ slug, path, url }` there, or null where it is
|
|
58
|
+
* absent or unpublished (draft mode: absent only). What a language switch links to. */
|
|
59
|
+
locales: Readonly<Record<string, PageContextAlternate | null>>;
|
|
60
|
+
};
|
|
61
|
+
/** The `page` prop of a view rendered outside the site (a test, a preview
|
|
62
|
+
* of a shape): a real object, so a view never guards an optional prop, but
|
|
63
|
+
* no position in the tree. Frozen: one singleton shared by every such
|
|
64
|
+
* render, so a view that pushed onto it would leak into the next. */
|
|
65
|
+
export declare const EMPTY_PAGE_CONTEXT: PageContext;
|
|
66
|
+
/** A paired view as the render layer sees it: anything carrying its schema's name. React's `renderPage` narrows it. */
|
|
67
|
+
export type PairedLike = {
|
|
68
|
+
schema: {
|
|
69
|
+
name: string;
|
|
70
|
+
title?: string;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/** The adapter's canvas net (spec 2026-09-27 section guard §5): called per section, in editor mode only.
|
|
74
|
+
* Framework-neutral: `child` and the return are the framework's node type (React: ReactNode). */
|
|
75
|
+
export type EditorWrap = (child: unknown, section: {
|
|
76
|
+
id: string;
|
|
77
|
+
title: string;
|
|
78
|
+
}) => unknown;
|
|
79
|
+
export type ZoneContent = {
|
|
80
|
+
readonly name: string;
|
|
81
|
+
readonly nodes: TreeNode[];
|
|
82
|
+
readonly components: ReadonlyMap<string, PairedLike>;
|
|
83
|
+
/** The config's CSS values behind the styles set (spec 2026-09-14 §2). */
|
|
84
|
+
readonly styleValues: StyleValues;
|
|
85
|
+
readonly editor: boolean;
|
|
86
|
+
/** Control flow the guard must let through — the adapter's (Next: unstable_rethrow; spec 2026-09-27 section guard §2). */
|
|
87
|
+
readonly rethrow?: (error: unknown) => void;
|
|
88
|
+
readonly editorWrap?: EditorWrap;
|
|
89
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The `page` prop of a view rendered outside the site (a test, a preview
|
|
2
|
+
* of a shape): a real object, so a view never guards an optional prop, but
|
|
3
|
+
* no position in the tree. Frozen: one singleton shared by every such
|
|
4
|
+
* render, so a view that pushed onto it would leak into the next. */
|
|
5
|
+
export const EMPTY_PAGE_CONTEXT = Object.freeze({
|
|
6
|
+
id: "", title: "", path: "/", url: "/", locale: "", fields: Object.freeze({}), ancestors: Object.freeze([]), children: Object.freeze([]),
|
|
7
|
+
locales: Object.freeze({}),
|
|
8
|
+
});
|
package/dist/pairing.d.ts
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
import type { ComponentType, ReactElement } from "react";
|
|
2
2
|
import type { BuilderMap, ElementSchema, SectionSchema, Values } from "./schema.ts";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Props } from "./view-props.ts";
|
|
4
4
|
import type { StyleProps } from "./styles.ts";
|
|
5
|
-
|
|
6
|
-
* in the tree; a section/element view gets field values flat, settings
|
|
7
|
-
* complete (defaults filled) and Smoodly's styles steps as stored (Default
|
|
8
|
-
* stores nothing — spec 2026-09-14 §2). The page branch keys on `kind` — an
|
|
9
|
-
* optional-only check would match section schemas too, since a type
|
|
10
|
-
* lacking `__zones` still extends `{ __zones?: X }`. */
|
|
11
|
-
export type Props<S> = S extends {
|
|
12
|
-
kind: "page";
|
|
13
|
-
__zones?: infer Z extends Record<string, unknown>;
|
|
14
|
-
__fields?: infer F extends BuilderMap;
|
|
15
|
-
} ? {
|
|
16
|
-
zones: Record<keyof Z & string, ZoneContent>;
|
|
17
|
-
page: PageContext<Values<F>>;
|
|
18
|
-
} : S extends {
|
|
19
|
-
__f?: infer F extends BuilderMap;
|
|
20
|
-
__s?: infer St extends BuilderMap;
|
|
21
|
-
} ? Values<F> & {
|
|
22
|
-
settings: Values<St>;
|
|
23
|
-
styles: StyleProps;
|
|
24
|
-
} : never;
|
|
5
|
+
export type { Props };
|
|
25
6
|
/** What hard-coded JSX may pass: fields flat, settings optional and partial, styles optional. */
|
|
26
7
|
type ExternalProps<F extends BuilderMap, St extends BuilderMap> = Values<F> & {
|
|
27
8
|
settings?: Partial<Values<St>>;
|
|
@@ -38,4 +19,3 @@ export declare function element<F extends BuilderMap, St extends BuilderMap>(sch
|
|
|
38
19
|
settings: Values<St>;
|
|
39
20
|
styles: StyleProps;
|
|
40
21
|
}>): PairedComponent<ElementSchema<F, St>, F, St>;
|
|
41
|
-
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Assembler } from "./assemble.ts";
|
|
2
|
+
import type { CollectionSchema } from "./collections.ts";
|
|
3
|
+
import type { EntryStore } from "./entry-store.ts";
|
|
4
|
+
import type { GlobalSchema } from "./global.ts";
|
|
5
|
+
import type { LocaleSet } from "./paths.ts";
|
|
6
|
+
import type { PublishedKey, PublishedStore } from "./published.ts";
|
|
7
|
+
import { type SectionLike } from "./refs.ts";
|
|
8
|
+
import type { SiteEntry, SitePage } from "./site.ts";
|
|
9
|
+
import type { PageStore } from "./store.ts";
|
|
10
|
+
/** The documents one projection writes before the rest are left to heal on read (§2). */
|
|
11
|
+
export declare const PROJECTION_CAP = 100;
|
|
12
|
+
/** `cap` overrides the projector's own for one call: a rebuild passes Infinity, a test a small number. */
|
|
13
|
+
export type ProjectOptions = {
|
|
14
|
+
cap?: number;
|
|
15
|
+
};
|
|
16
|
+
export type Projector = {
|
|
17
|
+
/** Writes the delivery rows of these pages in every supported locale (a row per visible locale, removed where not visible). Returns the keys it could not fit under the cap, already marked stale. */
|
|
18
|
+
pages(ids: string[], options?: ProjectOptions): Promise<PublishedKey[]>;
|
|
19
|
+
/** The same for entries and globals, by id (the collection is found through the registry). */
|
|
20
|
+
entries(ids: string[], options?: ProjectOptions): Promise<PublishedKey[]>;
|
|
21
|
+
/** One page document, one locale, written now: what heal-on-read calls. `seen`: the caller read a row for it,
|
|
22
|
+
* so a target no longer visible has a row to remove; without one nothing is written. The write is best effort:
|
|
23
|
+
* a failed put or remove is logged and the assembled document is served all the same. */
|
|
24
|
+
healPage(id: string, locale: string, options?: HealOptions): Promise<SitePage | null>;
|
|
25
|
+
healEntry(id: string, locale: string, options?: HealOptions): Promise<SiteEntry | null>;
|
|
26
|
+
healGlobal<V>(item: GlobalSchema<V>, locale: string, options?: HealOptions): Promise<V | null>;
|
|
27
|
+
/** Flags rows to rebuild on their next read: what an op does with the targets of a projection that threw. */
|
|
28
|
+
markStale(keys: PublishedKey[]): Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
export type HealOptions = {
|
|
31
|
+
seen?: boolean;
|
|
32
|
+
};
|
|
33
|
+
export type ProjectorDeps = {
|
|
34
|
+
assembler: Assembler;
|
|
35
|
+
stores: () => {
|
|
36
|
+
pages: PageStore;
|
|
37
|
+
entries: EntryStore;
|
|
38
|
+
published: PublishedStore;
|
|
39
|
+
};
|
|
40
|
+
collections: CollectionSchema<any>[];
|
|
41
|
+
globals: GlobalSchema[];
|
|
42
|
+
/** The ref walk behind a page row's tags. */
|
|
43
|
+
sections: SectionLike[];
|
|
44
|
+
elements?: SectionLike[];
|
|
45
|
+
locales: LocaleSet;
|
|
46
|
+
/** The fingerprint a row is current against: the site's cacheKey (§4). */
|
|
47
|
+
model: string;
|
|
48
|
+
cap?: number;
|
|
49
|
+
};
|
|
50
|
+
export declare function createProjector(deps: ProjectorDeps): Projector;
|
|
51
|
+
export type ProjectAllDeps = {
|
|
52
|
+
stores: {
|
|
53
|
+
pages: PageStore;
|
|
54
|
+
entries: EntryStore;
|
|
55
|
+
};
|
|
56
|
+
projector: Projector;
|
|
57
|
+
/** Every registered collection's and global's name: the entries are listed per name. */
|
|
58
|
+
names: string[];
|
|
59
|
+
/** Ids per batch; each batch is written whole, past the projector's cap. */
|
|
60
|
+
cap?: number;
|
|
61
|
+
};
|
|
62
|
+
/** The rebuild (spec 2026-10-07 delivery model §2): every page and entry of the space projected, in batches of
|
|
63
|
+
* `cap` ids, none left stale. Import ends with it, and `smoodly rebuild` is it. The counts are the pages and the
|
|
64
|
+
* entries (globals included) published in some locale; `stale` is what a batch could not write, 0 by design. */
|
|
65
|
+
export declare function projectAll(deps: ProjectAllDeps): Promise<{
|
|
66
|
+
pages: number;
|
|
67
|
+
entries: number;
|
|
68
|
+
stale: number;
|
|
69
|
+
}>;
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { ASSET_COLLECTION } from "./media.js";
|
|
2
|
+
import { collectEntryRefs, collectTreeRefs } from "./refs.js";
|
|
3
|
+
import { collectionTag, entryTag, globalTag, pageTag } from "./revalidate.js";
|
|
4
|
+
/** The documents one projection writes before the rest are left to heal on read (§2). */
|
|
5
|
+
export const PROJECTION_CAP = 100;
|
|
6
|
+
/** The entry ids a document depends on (media is not an entry). */
|
|
7
|
+
const entryTags = (edges) => edges.filter((e) => e.targetCollection !== ASSET_COLLECTION).map((e) => entryTag(e.targetId));
|
|
8
|
+
const unique = (tags) => [...new Set(tags)];
|
|
9
|
+
/** A heal's write is derived and the visitor's read must not fail on it (spec §2 Failure): logged, not thrown. */
|
|
10
|
+
async function bestEffort(what, write) {
|
|
11
|
+
try {
|
|
12
|
+
await write();
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
console.error(`smoodly: ${what} in the delivery tables failed; the next read rebuilds it.`, e);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function createProjector(deps) {
|
|
19
|
+
const { assembler, collections, globals, locales, model } = deps;
|
|
20
|
+
const cap = deps.cap ?? PROJECTION_CAP;
|
|
21
|
+
const isGlobal = (schema) => schema.kind === "global";
|
|
22
|
+
/** The page's row in the locale, or null where the public does not see it (absent, or an unpublished page). */
|
|
23
|
+
async function pageRow(a, record, all, locale) {
|
|
24
|
+
const row = record.locales[locale];
|
|
25
|
+
if (!row || (record.template !== null && row.status !== "published"))
|
|
26
|
+
return null;
|
|
27
|
+
const light = await a.treeRow(record, all, locale);
|
|
28
|
+
// A folder is a row without a document. A page the tree hides (path null) keeps one at its live address, which
|
|
29
|
+
// the address lookup never reaches but a locate can (a record at a code address): the read takes it by id then.
|
|
30
|
+
const path = light.hasPage ? light.path ?? (await deps.stores().pages.pathsOf(record.id))[locale] ?? null : null;
|
|
31
|
+
const document = path !== null ? await a.page(record.id, locale, path, { draft: false }) : null;
|
|
32
|
+
const raw = document ? await deps.stores().pages.getPublishedTree(record.id, locale) : null;
|
|
33
|
+
const refs = raw ? entryTags(collectTreeRefs(raw, deps.sections, deps.elements)) : [];
|
|
34
|
+
return {
|
|
35
|
+
...light, document,
|
|
36
|
+
tags: unique([pageTag(record.id), ...refs]),
|
|
37
|
+
model, stale: false, publishedAt: Date.now(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The schema an entry id belongs to, and its record: one `get` per registered collection until found. */
|
|
41
|
+
async function findEntry(id) {
|
|
42
|
+
const { entries } = deps.stores();
|
|
43
|
+
for (const schema of [...collections, ...globals]) {
|
|
44
|
+
const record = await entries.get(schema.name, id);
|
|
45
|
+
if (record)
|
|
46
|
+
return { schema, record };
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/** The entry's or global's row in the locale, or null where it is not published. A collection
|
|
51
|
+
* without a `path` gets a document too (its list items), with `path` null. */
|
|
52
|
+
async function entryRow(a, schema, record, locale) {
|
|
53
|
+
const row = record.locales[locale];
|
|
54
|
+
if (!row || row.status !== "published")
|
|
55
|
+
return null;
|
|
56
|
+
const { entries } = deps.stores();
|
|
57
|
+
const fields = (await entries.getMany(schema.name, [record.id], locale, { status: "published" }))[record.id];
|
|
58
|
+
if (!fields)
|
|
59
|
+
return null;
|
|
60
|
+
let document;
|
|
61
|
+
let path = null;
|
|
62
|
+
if (isGlobal(schema)) {
|
|
63
|
+
document = await a.global(schema, locale, { draft: false });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
path = schema.path !== undefined ? (await entries.pathsOf(schema.name, record.id))[locale] ?? null : null;
|
|
67
|
+
if (path !== null) {
|
|
68
|
+
document = await a.entry(record.id, locale, path, { draft: false });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// The list item is all an address-less entry serves: the same projection getEntries makes.
|
|
72
|
+
const [read] = await a.entries(schema.name, { locale, ids: [record.id] });
|
|
73
|
+
document = read && {
|
|
74
|
+
kind: "entry", locale, collection: schema.name, entry: { ...read.record, fields: read.item.fields },
|
|
75
|
+
path: null, locales: read.item.locales, ...(read.item.seo ? { seo: read.item.seo } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (document == null)
|
|
80
|
+
return null;
|
|
81
|
+
return {
|
|
82
|
+
id: record.id, locale, collection: schema.name, parentId: record.parentId, sort: record.sort, slug: row.slug, path,
|
|
83
|
+
createdAt: record.createdAt, updatedAt: row.updatedAt, fields, document,
|
|
84
|
+
tags: unique([
|
|
85
|
+
entryTag(record.id), collectionTag(schema.name), ...(isGlobal(schema) ? [globalTag(schema.name)] : []),
|
|
86
|
+
...entryTags(collectEntryRefs(fields, schema, deps.elements)),
|
|
87
|
+
]),
|
|
88
|
+
model, stale: false, publishedAt: Date.now(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Builds rows for each (id, locale) until the cap; the rest are marked stale and returned. */
|
|
92
|
+
async function project(cap, kind, targets, idOf, build, put) {
|
|
93
|
+
const { published } = deps.stores();
|
|
94
|
+
const rows = [];
|
|
95
|
+
const gone = [];
|
|
96
|
+
const over = [];
|
|
97
|
+
for (const t of targets) {
|
|
98
|
+
for (const locale of locales.supported) {
|
|
99
|
+
if (rows.length >= cap) {
|
|
100
|
+
over.push({ kind, id: idOf(t), locale });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const row = await build(t, locale);
|
|
104
|
+
if (row)
|
|
105
|
+
rows.push(row);
|
|
106
|
+
else
|
|
107
|
+
gone.push({ kind, id: idOf(t), locale });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (rows.length > 0)
|
|
111
|
+
await put(rows);
|
|
112
|
+
if (gone.length > 0)
|
|
113
|
+
await published.remove(gone);
|
|
114
|
+
if (over.length > 0)
|
|
115
|
+
await published.markStale(over);
|
|
116
|
+
return over;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
async pages(ids, options) {
|
|
120
|
+
const all = await deps.stores().pages.listPages();
|
|
121
|
+
const a = assembler.batch({ pages: all });
|
|
122
|
+
const byId = new Map(all.map((p) => [p.id, p]));
|
|
123
|
+
// A page no longer in the tree has no row in any locale.
|
|
124
|
+
const missing = ids.filter((id) => !byId.has(id));
|
|
125
|
+
if (missing.length > 0)
|
|
126
|
+
await deps.stores().published.remove(missing.map((id) => ({ kind: "page", id })));
|
|
127
|
+
return project(options?.cap ?? cap, "page", ids.filter((id) => byId.has(id)).map((id) => byId.get(id)), (p) => p.id, (p, locale) => pageRow(a, p, all, locale), (rows) => deps.stores().published.putPages(rows));
|
|
128
|
+
},
|
|
129
|
+
async entries(ids, options) {
|
|
130
|
+
const a = assembler.batch();
|
|
131
|
+
const found = [];
|
|
132
|
+
const missing = [];
|
|
133
|
+
for (const id of ids) {
|
|
134
|
+
const hit = await findEntry(id);
|
|
135
|
+
if (hit)
|
|
136
|
+
found.push(hit);
|
|
137
|
+
else
|
|
138
|
+
missing.push({ kind: "entry", id });
|
|
139
|
+
}
|
|
140
|
+
if (missing.length > 0)
|
|
141
|
+
await deps.stores().published.remove(missing);
|
|
142
|
+
return project(options?.cap ?? cap, "entry", found, (h) => h.record.id, (h, locale) => entryRow(a, h.schema, h.record, locale), (rows) => deps.stores().published.putEntries(rows));
|
|
143
|
+
},
|
|
144
|
+
async healPage(id, locale, options) {
|
|
145
|
+
const { pages, published } = deps.stores();
|
|
146
|
+
// One record first: a page the public does not see in the locale costs no table read.
|
|
147
|
+
const record = await pages.getPage(id);
|
|
148
|
+
const row = record?.locales[locale];
|
|
149
|
+
const visible = row !== undefined && (record.template === null || row.status === "published");
|
|
150
|
+
const all = visible ? await pages.listPages() : [];
|
|
151
|
+
const projected = visible ? await pageRow(assembler.batch({ pages: all }), record, all, locale) : null;
|
|
152
|
+
if (!projected) {
|
|
153
|
+
if (options?.seen !== false)
|
|
154
|
+
await bestEffort(`removing page ${id} (${locale})`, () => published.remove([{ kind: "page", id, locale }]));
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
await bestEffort(`writing page ${id} (${locale})`, () => published.putPages([projected]));
|
|
158
|
+
return projected.document;
|
|
159
|
+
},
|
|
160
|
+
async healEntry(id, locale, options) {
|
|
161
|
+
const { published } = deps.stores();
|
|
162
|
+
const hit = await findEntry(id);
|
|
163
|
+
const row = hit ? await entryRow(assembler.batch(), hit.schema, hit.record, locale) : null;
|
|
164
|
+
if (!row) {
|
|
165
|
+
if (options?.seen !== false)
|
|
166
|
+
await bestEffort(`removing entry ${id} (${locale})`, () => published.remove([{ kind: "entry", id, locale }]));
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
await bestEffort(`writing entry ${id} (${locale})`, () => published.putEntries([row]));
|
|
170
|
+
return row.document;
|
|
171
|
+
},
|
|
172
|
+
async healGlobal(item, locale, options) {
|
|
173
|
+
const { entries, published } = deps.stores();
|
|
174
|
+
const [record] = await entries.list(item.name);
|
|
175
|
+
if (!record)
|
|
176
|
+
return null;
|
|
177
|
+
const row = await entryRow(assembler.batch(), item, record, locale);
|
|
178
|
+
if (!row) {
|
|
179
|
+
if (options?.seen !== false)
|
|
180
|
+
await bestEffort(`removing global ${item.name} (${locale})`, () => published.remove([{ kind: "entry", id: record.id, locale }]));
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
await bestEffort(`writing global ${item.name} (${locale})`, () => published.putEntries([row]));
|
|
184
|
+
return row.document;
|
|
185
|
+
},
|
|
186
|
+
markStale: (keys) => deps.stores().published.markStale(keys),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** The rebuild (spec 2026-10-07 delivery model §2): every page and entry of the space projected, in batches of
|
|
190
|
+
* `cap` ids, none left stale. Import ends with it, and `smoodly rebuild` is it. The counts are the pages and the
|
|
191
|
+
* entries (globals included) published in some locale; `stale` is what a batch could not write, 0 by design. */
|
|
192
|
+
export async function projectAll(deps) {
|
|
193
|
+
const size = deps.cap ?? PROJECTION_CAP;
|
|
194
|
+
const whole = { cap: Infinity };
|
|
195
|
+
const published = (r) => Object.values(r.locales).some((l) => l?.status === "published");
|
|
196
|
+
let stale = 0;
|
|
197
|
+
const pages = await deps.stores.pages.listPages();
|
|
198
|
+
for (let i = 0; i < pages.length; i += size) {
|
|
199
|
+
stale += (await deps.projector.pages(pages.slice(i, i + size).map((p) => p.id), whole)).length;
|
|
200
|
+
}
|
|
201
|
+
const entries = (await Promise.all(deps.names.map((name) => deps.stores.entries.list(name)))).flat();
|
|
202
|
+
for (let i = 0; i < entries.length; i += size) {
|
|
203
|
+
stale += (await deps.projector.entries(entries.slice(i, i + size).map((e) => e.id), whole)).length;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
pages: pages.filter((p) => p.template !== null && published(p)).length,
|
|
207
|
+
entries: entries.filter(published).length,
|
|
208
|
+
stale,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { EntryOrder } from "./collections.ts";
|
|
2
|
+
import { type Where } from "./entry-store.ts";
|
|
3
|
+
import type { SiteEntry, SitePage } from "./site.ts";
|
|
4
|
+
/** A page's delivery row: a published page, or a folder present in the locale (document null, hasPage false). */
|
|
5
|
+
export type PublishedPageRow = {
|
|
6
|
+
id: string;
|
|
7
|
+
locale: string;
|
|
8
|
+
parentId: string | null;
|
|
9
|
+
sort: number | null;
|
|
10
|
+
/** The served address; null when the tree hides the node (no path, or a code address wins). */
|
|
11
|
+
path: string | null;
|
|
12
|
+
title: string;
|
|
13
|
+
hasPage: boolean;
|
|
14
|
+
/** The page fields with defaults, raw (no asset resolution): what a tree item and a link show. */
|
|
15
|
+
linkFields: Record<string, unknown>;
|
|
16
|
+
document: SitePage | null;
|
|
17
|
+
tags: string[];
|
|
18
|
+
model: string;
|
|
19
|
+
stale: boolean;
|
|
20
|
+
publishedAt: number;
|
|
21
|
+
};
|
|
22
|
+
/** An entry's or a global's delivery row. A global's `document` is the value `getGlobal` returns; its collection is the item's name. */
|
|
23
|
+
export type PublishedEntryRow = {
|
|
24
|
+
id: string;
|
|
25
|
+
locale: string;
|
|
26
|
+
collection: string;
|
|
27
|
+
parentId: string | null;
|
|
28
|
+
sort: number | null;
|
|
29
|
+
slug: string | null;
|
|
30
|
+
path: string | null;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
/** The published snapshot, raw, slug included: what `where` and `order` read. */
|
|
34
|
+
fields: Record<string, unknown>;
|
|
35
|
+
document: SiteEntry | unknown;
|
|
36
|
+
tags: string[];
|
|
37
|
+
model: string;
|
|
38
|
+
stale: boolean;
|
|
39
|
+
publishedAt: number;
|
|
40
|
+
};
|
|
41
|
+
/** `globals`: present when the lookup asked for global items by name — the locale's rows of those collections,
|
|
42
|
+
* as `entries` returns them (document, model, stale; the raw fields may be empty), so a page is one call. */
|
|
43
|
+
export type PublishedRoute = ({
|
|
44
|
+
kind: "page";
|
|
45
|
+
row: PublishedPageRow;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "entry";
|
|
48
|
+
row: PublishedEntryRow;
|
|
49
|
+
}) & {
|
|
50
|
+
globals?: PublishedEntryRow[];
|
|
51
|
+
};
|
|
52
|
+
/** `model` and `stale` ride along so a tree read can rebuild what is behind before answering (spec 2026-10-07 §4). */
|
|
53
|
+
export type PublishedTreeRow = Pick<PublishedPageRow, "id" | "parentId" | "sort" | "path" | "title" | "hasPage" | "linkFields" | "model" | "stale">;
|
|
54
|
+
export type PublishedQuery = {
|
|
55
|
+
locale: string;
|
|
56
|
+
where?: Where;
|
|
57
|
+
order?: EntryOrder;
|
|
58
|
+
limit?: number;
|
|
59
|
+
parent?: string | null;
|
|
60
|
+
ids?: string[];
|
|
61
|
+
};
|
|
62
|
+
export type PublishedKey = {
|
|
63
|
+
kind: "page" | "entry";
|
|
64
|
+
id: string;
|
|
65
|
+
locale?: string;
|
|
66
|
+
};
|
|
67
|
+
export interface PublishedStore {
|
|
68
|
+
/** Upsert by (id, locale). */
|
|
69
|
+
putPages(rows: PublishedPageRow[]): Promise<void>;
|
|
70
|
+
putEntries(rows: PublishedEntryRow[]): Promise<void>;
|
|
71
|
+
/** Every locale of the id when `locale` is absent. */
|
|
72
|
+
remove(keys: PublishedKey[]): Promise<void>;
|
|
73
|
+
/** The one-call lookup: a reachable page or entry at the address, else null. With `globals` (collection names of
|
|
74
|
+
* global items), the locale's rows of those collections come along on the route (schema v9), still one call. */
|
|
75
|
+
byAddress(locale: string, path: string, globals?: readonly string[]): Promise<PublishedRoute | null>;
|
|
76
|
+
page(id: string, locale: string): Promise<PublishedPageRow | null>;
|
|
77
|
+
entry(id: string, locale: string): Promise<PublishedEntryRow | null>;
|
|
78
|
+
/** Filtered, ordered and limited over `fields`, in the store (SQL or the memory matcher). */
|
|
79
|
+
entries(collection: string, q: PublishedQuery): Promise<PublishedEntryRow[]>;
|
|
80
|
+
/** The light columns of every row in the locale, with the model and stale flag, in tree order (sort asc nulls last, published, id). */
|
|
81
|
+
tree(locale: string): Promise<PublishedTreeRow[]>;
|
|
82
|
+
markStale(keys: PublishedKey[]): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export declare class MemoryPublishedStore implements PublishedStore {
|
|
85
|
+
private pages;
|
|
86
|
+
private entryRows;
|
|
87
|
+
putPages(rows: PublishedPageRow[]): Promise<void>;
|
|
88
|
+
putEntries(rows: PublishedEntryRow[]): Promise<void>;
|
|
89
|
+
remove(keys: PublishedKey[]): Promise<void>;
|
|
90
|
+
byAddress(locale: string, path: string, globals?: readonly string[]): Promise<PublishedRoute | null>;
|
|
91
|
+
page(id: string, locale: string): Promise<PublishedPageRow | null>;
|
|
92
|
+
entry(id: string, locale: string): Promise<PublishedEntryRow | null>;
|
|
93
|
+
entries(collection: string, q: PublishedQuery): Promise<PublishedEntryRow[]>;
|
|
94
|
+
tree(locale: string): Promise<PublishedTreeRow[]>;
|
|
95
|
+
markStale(keys: PublishedKey[]): Promise<void>;
|
|
96
|
+
}
|