smoodly 0.0.6 → 0.0.7
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 +231 -73
- package/dist/admin/client-ops.js +2 -2
- package/dist/admin/editor/EditorView.js +167 -24
- package/dist/admin/editor/PageSettings.d.ts +8 -4
- package/dist/admin/editor/PageSettings.js +25 -25
- package/dist/admin/fixed-nodes.d.ts +16 -10
- package/dist/admin/fixed-nodes.js +55 -41
- package/dist/admin/ops-impl.js +134 -35
- package/dist/admin/ops.d.ts +51 -11
- package/dist/admin/shell/AdminApp.js +3 -32
- package/dist/admin/shell/EntriesList.d.ts +7 -0
- package/dist/admin/shell/EntriesList.js +95 -0
- package/dist/admin/shell/EntryForm.js +167 -49
- package/dist/admin/shell/PagesList.js +86 -26
- package/dist/admin/shell/entries-list.d.ts +21 -0
- package/dist/admin/shell/entries-list.js +36 -0
- package/dist/admin/shell/pages-tree.d.ts +30 -12
- package/dist/admin/shell/pages-tree.js +48 -14
- package/dist/admin/ui/LocaleSwitcher.d.ts +11 -0
- package/dist/admin/ui/LocaleSwitcher.js +15 -0
- package/dist/collections.d.ts +6 -0
- package/dist/collections.js +16 -0
- package/dist/config.js +23 -3
- package/dist/entry-store.d.ts +132 -54
- package/dist/entry-store.js +294 -86
- package/dist/index.d.ts +6 -6
- package/dist/index.js +4 -4
- package/dist/localize.d.ts +0 -11
- package/dist/localize.js +11 -29
- package/dist/paths.d.ts +46 -27
- package/dist/paths.js +62 -29
- package/dist/revalidate.js +2 -1
- package/dist/site.d.ts +6 -2
- package/dist/site.js +65 -39
- package/dist/sql-space.d.ts +1 -1
- package/dist/sql-space.js +13 -5
- package/dist/sql.js +96 -53
- package/dist/store.d.ts +66 -32
- package/dist/store.js +205 -90
- package/dist/supabase-entry-store.d.ts +29 -8
- package/dist/supabase-entry-store.js +341 -177
- package/dist/supabase-store.d.ts +21 -9
- package/dist/supabase-store.js +202 -111
- package/package.json +1 -1
package/dist/sql.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
// security_invoker so it runs under the caller's own RLS policies
|
|
6
6
|
// rather than the view owner's privileges (PostgREST-native reads,
|
|
7
7
|
// SQL-queryable eject story) — and a partial expression index for a
|
|
8
|
-
// declared order field, on the jsonb VALUE (slug uniqueness
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// declared order field, on the jsonb VALUE (sibling slug uniqueness
|
|
9
|
+
// is the `paths` primary key plus the stores' per-locale pre-check,
|
|
10
|
+
// not an index). A view joins the locale rows, one row per published
|
|
11
|
+
// locale (spec 2026-09-06 §7); a versioned collection reads its
|
|
12
|
+
// fields from the published snapshot.
|
|
11
13
|
import { BUILT_IN_ORDER_FIELDS } from "./collections.js";
|
|
12
14
|
import { currentSpaceSQL, SPACE_COL, spaceLayerSQL } from "./sql-space.js";
|
|
13
15
|
const snake = (s) => s.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
@@ -16,42 +18,50 @@ const q = (s) => `"${s}"`;
|
|
|
16
18
|
// immutable — the order field's index stays on the raw jsonb
|
|
17
19
|
// expression: jsonb ordering compares numbers numerically and strings,
|
|
18
20
|
// ISO dates included, alphabetically, matching the memory comparator).
|
|
19
|
-
function fieldExpr(key, d) {
|
|
21
|
+
function fieldExpr(key, d, source) {
|
|
20
22
|
switch (d.type) {
|
|
21
23
|
case "text":
|
|
22
24
|
case "link":
|
|
23
25
|
case "slug":
|
|
24
26
|
case "select":
|
|
25
27
|
case "ref": // stored as the target entry's id string
|
|
26
|
-
return
|
|
28
|
+
return `${source}->>'${key}'`;
|
|
27
29
|
case "number":
|
|
28
|
-
return `(
|
|
30
|
+
return `(${source}->>'${key}')::numeric`;
|
|
29
31
|
case "date":
|
|
30
|
-
return `(
|
|
32
|
+
return `(${source}->>'${key}')::timestamptz`;
|
|
31
33
|
default:
|
|
32
|
-
return
|
|
34
|
+
return `${source}->'${key}'`; // image, video, file, object, richtext, dataAttributes, refList
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
export function collectionSQL(collection) {
|
|
36
38
|
const name = collection.name;
|
|
37
|
-
const
|
|
39
|
+
const versioned = collection.versions === true;
|
|
40
|
+
// Where a field's value lives: a versioned collection's view reads the
|
|
41
|
+
// published snapshot (merged at publish time); otherwise shared fields
|
|
42
|
+
// come from the node and localized ones from the locale row.
|
|
43
|
+
const sourceOf = (d) => (versioned ? 'v."fields"' : d.localized === true ? 'l."fields"' : 'e."fields"');
|
|
44
|
+
const columns = ['e."id"', 'l."locale"', 'l."slug"', 'e."sort"', 'e."created_at"', 'l."updated_at"'];
|
|
38
45
|
for (const [key, d] of Object.entries(collection.fields)) {
|
|
39
|
-
|
|
46
|
+
if (key === "slug")
|
|
47
|
+
continue; // the segment is the row's column, never a field
|
|
48
|
+
columns.push(`${fieldExpr(key, d, sourceOf(d))} as ${q(snake(key))}`);
|
|
40
49
|
}
|
|
41
50
|
const statements = [
|
|
42
51
|
[
|
|
43
52
|
`create or replace view ${q(name)} with (security_invoker = true) as`,
|
|
44
53
|
`select`,
|
|
45
54
|
columns.map((c) => " " + c).join(",\n"),
|
|
46
|
-
`from "entries"`,
|
|
47
|
-
`
|
|
55
|
+
`from "entries" e`,
|
|
56
|
+
`join "entry_locales" l on l."entry_id" = e."id"`,
|
|
57
|
+
...(versioned ? [`join "entry_versions" v on v."id" = l."published_version_id"`] : []),
|
|
58
|
+
`where e."collection" = '${name}' and l."status" = 'published';`,
|
|
48
59
|
].join("\n"),
|
|
49
60
|
];
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// same one the adapter orders by, so the index actually serves the sort.
|
|
61
|
+
// Only a declared order field needs a per-collection expression index —
|
|
62
|
+
// on the node's shared fields (an order field can be neither localized
|
|
63
|
+
// nor `slug`, which collection() refuses), the jsonb value (`->`) the
|
|
64
|
+
// adapter orders by, so the index serves the sort.
|
|
55
65
|
const order = collection.order;
|
|
56
66
|
if (order !== undefined && order !== "manual" && !BUILT_IN_ORDER_FIELDS.includes(order.by)) {
|
|
57
67
|
statements.push(`create index if not exists ${q(`entries_${name}_${snake(order.by)}_idx`)}\n` +
|
|
@@ -71,28 +81,34 @@ create table if not exists "pages" (
|
|
|
71
81
|
"id" uuid primary key default gen_random_uuid(),
|
|
72
82
|
${SPACE_COL},
|
|
73
83
|
"parent_id" uuid references "pages" ("id") on delete restrict,
|
|
74
|
-
"slug" text not null,
|
|
75
|
-
"title" text not null,
|
|
76
|
-
"i18n" jsonb,
|
|
77
84
|
"template" text,
|
|
78
|
-
"status" text not null default 'draft',
|
|
79
85
|
"sort" numeric,
|
|
80
|
-
"locales" text[],
|
|
81
|
-
"draft_version_id" uuid,
|
|
82
|
-
"published_version_id" uuid,
|
|
83
86
|
"created_at" timestamptz not null default now(),
|
|
84
87
|
"updated_at" timestamptz not null default now()
|
|
85
88
|
);
|
|
86
89
|
|
|
87
|
-
-- The tree is the URL (spec 2026-09-05 §1): a node stores its
|
|
88
|
-
--
|
|
89
|
-
--
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
-- The tree is the URL (spec 2026-09-05 §1): a node stores its parent and
|
|
91
|
+
-- its position; template null = folder. Everything a locale owns — the
|
|
92
|
+
-- node's own segment, title, publish state and version pointers — lives
|
|
93
|
+
-- in page_locales, one row per locale the page exists in (spec 2026-09-06
|
|
94
|
+
-- §2). There is no sibling-slug index: URL uniqueness per locale is the
|
|
95
|
+
-- primary key of "paths", which the stores write inside the same operation.
|
|
93
96
|
create index if not exists "pages_parent_idx"
|
|
94
97
|
on "pages" ("space_id", "parent_id", "sort");
|
|
95
98
|
|
|
99
|
+
create table if not exists "page_locales" (
|
|
100
|
+
${SPACE_COL},
|
|
101
|
+
"page_id" uuid not null references "pages" ("id") on delete cascade,
|
|
102
|
+
"locale" text not null,
|
|
103
|
+
"slug" text not null,
|
|
104
|
+
"title" text not null,
|
|
105
|
+
"status" text not null default 'draft',
|
|
106
|
+
"draft_version_id" uuid,
|
|
107
|
+
"published_version_id" uuid,
|
|
108
|
+
"updated_at" timestamptz not null default now(),
|
|
109
|
+
primary key ("page_id", "locale")
|
|
110
|
+
);
|
|
111
|
+
|
|
96
112
|
-- Sibling reorder for pages: same shape as smoodly_reorder for entries.
|
|
97
113
|
create or replace function "smoodly_reorder_pages"(p_ids uuid[]) returns void language sql as $$
|
|
98
114
|
update "pages" pg set "sort" = u.ord
|
|
@@ -101,15 +117,21 @@ create or replace function "smoodly_reorder_pages"(p_ids uuid[]) returns void la
|
|
|
101
117
|
and pg."space_id" = "smoodly_current_space"();
|
|
102
118
|
$$;
|
|
103
119
|
|
|
120
|
+
-- A version belongs to ONE locale of one page: each locale has its own
|
|
121
|
+
-- draft, its own publish and its own history.
|
|
104
122
|
create table if not exists "page_versions" (
|
|
105
123
|
"id" uuid primary key default gen_random_uuid(),
|
|
106
124
|
${SPACE_COL},
|
|
107
125
|
"page_id" uuid not null references "pages" ("id") on delete cascade,
|
|
126
|
+
"locale" text not null,
|
|
108
127
|
"tree" jsonb not null,
|
|
109
128
|
"created_by" text,
|
|
110
129
|
"created_at" timestamptz not null default now()
|
|
111
130
|
);
|
|
112
131
|
|
|
132
|
+
create index if not exists "page_versions_page_locale_idx"
|
|
133
|
+
on "page_versions" ("page_id", "locale", "created_at");
|
|
134
|
+
|
|
113
135
|
create table if not exists "saved_sections" (
|
|
114
136
|
"id" uuid primary key default gen_random_uuid(),
|
|
115
137
|
${SPACE_COL},
|
|
@@ -132,40 +154,58 @@ create table if not exists "entries" (
|
|
|
132
154
|
"collection" text not null,
|
|
133
155
|
"parent_id" uuid references "entries" ("id") on delete restrict,
|
|
134
156
|
"fields" jsonb not null default '{}'::jsonb,
|
|
135
|
-
"i18n" jsonb,
|
|
136
|
-
"status" text not null default 'draft',
|
|
137
157
|
"sort" numeric,
|
|
138
|
-
"locales" text[],
|
|
139
158
|
"created_at" timestamptz not null default now(),
|
|
140
159
|
"updated_at" timestamptz not null default now()
|
|
141
160
|
);
|
|
142
161
|
|
|
143
|
-
--
|
|
144
|
-
--
|
|
145
|
-
--
|
|
146
|
-
--
|
|
147
|
-
--
|
|
148
|
-
--
|
|
149
|
-
--
|
|
150
|
-
--
|
|
151
|
-
-- a number, boolean or null slug would otherwise print as non-empty
|
|
152
|
-
-- text and claim the index; only a non-empty string slug claims a URL,
|
|
153
|
-
-- absent, null, empty or non-string slugs never conflict — the same
|
|
154
|
-
-- rule as slugOf in paths.ts. A flat collection (every entry a root)
|
|
155
|
-
-- therefore behaves exactly as per-collection uniqueness did.
|
|
156
|
-
create unique index if not exists "entries_sibling_slug_key"
|
|
157
|
-
on "entries" ("space_id", "collection", "parent_id", ("fields"->>'slug')) nulls not distinct
|
|
158
|
-
where (jsonb_typeof("fields"->'slug') = 'string' and "fields"->>'slug' <> '');
|
|
159
|
-
|
|
160
|
-
create index if not exists "entries_collection_status_idx"
|
|
161
|
-
on "entries" ("space_id", "collection", "status");
|
|
162
|
-
|
|
162
|
+
-- The node holds identity, tree position and the SHARED fields — every
|
|
163
|
+
-- field without .localized() (spec 2026-09-06 §7): a Person's photo is
|
|
164
|
+
-- the same in every language. Everything a locale owns — its segment,
|
|
165
|
+
-- publish state, the .localized() fields and the version pointers —
|
|
166
|
+
-- lives in entry_locales, one row per locale the entry exists in. There
|
|
167
|
+
-- is no sibling-slug index any more: URL uniqueness per locale is the
|
|
168
|
+
-- primary key of "paths", which the stores write inside the same
|
|
169
|
+
-- operation, and both adapters pre-check siblings per locale in code.
|
|
163
170
|
create index if not exists "entries_collection_sort_idx"
|
|
164
171
|
on "entries" ("space_id", "collection", "sort");
|
|
165
172
|
|
|
166
173
|
create index if not exists "entries_parent_idx"
|
|
167
174
|
on "entries" ("space_id", "collection", "parent_id");
|
|
168
175
|
|
|
176
|
+
create table if not exists "entry_locales" (
|
|
177
|
+
${SPACE_COL},
|
|
178
|
+
"entry_id" uuid not null references "entries" ("id") on delete cascade,
|
|
179
|
+
"locale" text not null,
|
|
180
|
+
"slug" text,
|
|
181
|
+
"status" text not null default 'draft',
|
|
182
|
+
"fields" jsonb not null default '{}'::jsonb,
|
|
183
|
+
"draft_version_id" uuid,
|
|
184
|
+
"published_version_id" uuid,
|
|
185
|
+
"updated_at" timestamptz not null default now(),
|
|
186
|
+
primary key ("entry_id", "locale")
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
create index if not exists "entry_locales_status_idx"
|
|
190
|
+
on "entry_locales" ("space_id", "locale", "status");
|
|
191
|
+
|
|
192
|
+
-- A version belongs to ONE locale of one entry and stores the MERGED
|
|
193
|
+
-- fields (shared + localized + slug) as they were at that save, so
|
|
194
|
+
-- restoring an old Finnish version restores the shared fields too.
|
|
195
|
+
-- Rows exist only for collections registered with \`versions: true\`.
|
|
196
|
+
create table if not exists "entry_versions" (
|
|
197
|
+
"id" uuid primary key default gen_random_uuid(),
|
|
198
|
+
${SPACE_COL},
|
|
199
|
+
"entry_id" uuid not null references "entries" ("id") on delete cascade,
|
|
200
|
+
"locale" text not null,
|
|
201
|
+
"fields" jsonb not null,
|
|
202
|
+
"created_by" text,
|
|
203
|
+
"created_at" timestamptz not null default now()
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
create index if not exists "entry_versions_entry_locale_idx"
|
|
207
|
+
on "entry_versions" ("entry_id", "locale", "created_at");
|
|
208
|
+
|
|
169
209
|
-- v1 reorder (DESIGN.md §3 Manual ordering): rewrite sort 1..N in ONE
|
|
170
210
|
-- statement. Deliberately does not touch updated_at — a drag must not
|
|
171
211
|
-- make the whole collection look freshly edited. Scoped to the current
|
|
@@ -231,10 +271,13 @@ $$;
|
|
|
231
271
|
-- (collectionSQL) run with security_invoker = true, so they are subject
|
|
232
272
|
-- to this same smoodly_read policy rather than their owner's privileges.
|
|
233
273
|
alter table "pages" enable row level security;
|
|
274
|
+
alter table "page_locales" enable row level security;
|
|
234
275
|
alter table "page_versions" enable row level security;
|
|
235
276
|
alter table "saved_sections" enable row level security;
|
|
236
277
|
alter table "global_sections" enable row level security;
|
|
237
278
|
alter table "entries" enable row level security;
|
|
279
|
+
alter table "entry_locales" enable row level security;
|
|
280
|
+
alter table "entry_versions" enable row level security;
|
|
238
281
|
alter table "refs" enable row level security;
|
|
239
282
|
alter table "paths" enable row level security;
|
|
240
283
|
`.trim(),
|
package/dist/store.d.ts
CHANGED
|
@@ -4,36 +4,43 @@ import { type RefEdge, type SectionLike } from "./refs.ts";
|
|
|
4
4
|
import type { MemoryRefIndex } from "./ref-index.ts";
|
|
5
5
|
import { type PathIndex } from "./path-index.ts";
|
|
6
6
|
import { type LocaleSet, type PagePathOptions, type PathTarget } from "./paths.ts";
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export type PageLocale = {
|
|
8
|
+
/** The node's OWN segment in this locale; the URL is derived (paths.ts). */
|
|
9
|
+
slug: string;
|
|
10
|
+
title: string;
|
|
11
|
+
status: "draft" | "published";
|
|
12
|
+
draftVersionId: string | null;
|
|
13
|
+
publishedVersionId: string | null;
|
|
14
|
+
};
|
|
11
15
|
export type PageRecord = {
|
|
12
16
|
id: string;
|
|
13
17
|
parentId: string | null;
|
|
14
|
-
/** The node's OWN segment in the default locale; the URL is derived (paths.ts). */
|
|
15
|
-
slug: string;
|
|
16
|
-
title: string;
|
|
17
|
-
i18n: PageI18n | null;
|
|
18
18
|
/** null = folder: a node with a title and a position but no page behind it. */
|
|
19
19
|
template: string | null;
|
|
20
|
-
status: "draft" | "published";
|
|
21
20
|
sort: number | null;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
publishedVersionId: string | null;
|
|
21
|
+
/** One entry per locale the page exists in. */
|
|
22
|
+
locales: Record<string, PageLocale>;
|
|
25
23
|
};
|
|
26
24
|
export type CreatePageInput = {
|
|
25
|
+
/** The one locale the page is created in; others are added with addLocale. */
|
|
26
|
+
locale: string;
|
|
27
27
|
slug: string;
|
|
28
28
|
template: string | null;
|
|
29
29
|
title?: string;
|
|
30
30
|
parentId?: string | null;
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
};
|
|
32
|
+
/** `from` names the locale whose draft tree, title and slug are copied;
|
|
33
|
+
* `slug` and `title` override the copies (the materializer's per-locale
|
|
34
|
+
* segments for fixed pages). */
|
|
35
|
+
export type AddLocaleOptions = {
|
|
36
|
+
from: string;
|
|
37
|
+
slug?: string;
|
|
38
|
+
title?: string;
|
|
33
39
|
};
|
|
34
40
|
export type PageVersion = {
|
|
35
41
|
id: string;
|
|
36
42
|
pageId: string;
|
|
43
|
+
locale: string;
|
|
37
44
|
tree: PageTree;
|
|
38
45
|
createdAt: number;
|
|
39
46
|
};
|
|
@@ -42,7 +49,13 @@ export interface PageStore {
|
|
|
42
49
|
getPage(id: string): Promise<PageRecord | null>;
|
|
43
50
|
listPages(): Promise<PageRecord[]>;
|
|
44
51
|
resolvePath(locale: string, path: string): Promise<PathTarget | null>;
|
|
52
|
+
/** locale → path, for the locales the page exists in. */
|
|
45
53
|
pathsOf(id: string): Promise<Record<string, string>>;
|
|
54
|
+
/** Refused when the page already has the locale or its parent lacks it (the parent rule). */
|
|
55
|
+
addLocale(id: string, locale: string, options: AddLocaleOptions): Promise<PageRecord>;
|
|
56
|
+
/** Deletes the row, its versions and its paths. Refused while a
|
|
57
|
+
* descendant has the locale, and for the page's last locale. */
|
|
58
|
+
removeLocale(id: string, locale: string): Promise<PageRecord>;
|
|
46
59
|
rename(id: string, patch: {
|
|
47
60
|
locale: string;
|
|
48
61
|
slug?: string;
|
|
@@ -53,17 +66,19 @@ export interface PageStore {
|
|
|
53
66
|
* final 0-based position among the destination siblings: the node is
|
|
54
67
|
* removed from the sibling list first, then spliced back in at `index`.
|
|
55
68
|
* Omitted, it appends; out-of-range values clamp to [0, siblings.length].
|
|
69
|
+
* Refused when the destination lacks one of the page's locales.
|
|
56
70
|
*/
|
|
57
71
|
move(id: string, to: {
|
|
58
72
|
parentId: string | null;
|
|
59
73
|
index?: number;
|
|
60
74
|
}): Promise<PageRecord>;
|
|
75
|
+
/** A folder becomes a page: an empty draft version per present locale. */
|
|
61
76
|
setTemplate(id: string, template: string): Promise<PageRecord>;
|
|
62
|
-
saveDraft(id: string, tree: PageTree): Promise<PageVersion>;
|
|
63
|
-
publish(id: string): Promise<PageRecord>;
|
|
64
|
-
getDraftTree(id: string): Promise<PageTree | null>;
|
|
65
|
-
getPublishedTree(id: string): Promise<PageTree | null>;
|
|
66
|
-
listVersions(id: string): Promise<PageVersion[]>;
|
|
77
|
+
saveDraft(id: string, locale: string, tree: PageTree): Promise<PageVersion>;
|
|
78
|
+
publish(id: string, locale: string): Promise<PageRecord>;
|
|
79
|
+
getDraftTree(id: string, locale: string): Promise<PageTree | null>;
|
|
80
|
+
getPublishedTree(id: string, locale: string): Promise<PageTree | null>;
|
|
81
|
+
listVersions(id: string, locale: string): Promise<PageVersion[]>;
|
|
67
82
|
deletePage(id: string): Promise<void>;
|
|
68
83
|
}
|
|
69
84
|
/** What a PageStore needs beyond its tables to maintain the refs index
|
|
@@ -94,16 +109,26 @@ export declare const pageErrors: {
|
|
|
94
109
|
homeChildren: () => Error;
|
|
95
110
|
cycle: () => Error;
|
|
96
111
|
hasChildren: (id: string) => Error;
|
|
112
|
+
unsupportedLocale: (locale: string) => Error;
|
|
113
|
+
notInLocale: (id: string, locale: string) => Error;
|
|
114
|
+
alreadyInLocale: (id: string, locale: string) => Error;
|
|
115
|
+
parentNotInLocale: (locale: string) => Error;
|
|
116
|
+
lastLocale: (id: string) => Error;
|
|
117
|
+
localeInUse: (locale: string, titles: string[]) => Error;
|
|
118
|
+
moveLocales: (locale: string) => Error;
|
|
97
119
|
};
|
|
98
|
-
export
|
|
99
|
-
/**
|
|
100
|
-
|
|
120
|
+
export { assertSupportedLocale } from "./paths.ts";
|
|
121
|
+
/** The page's row for a locale — the "does not exist in locale" guard every per-locale write runs. */
|
|
122
|
+
export declare function localeRow(page: PageRecord, locale: string): PageLocale;
|
|
123
|
+
/** A page's refs rows cover EVERY live tree — each locale's draft and
|
|
124
|
+
* published trees — so an entry stays delete-guarded while any of them
|
|
101
125
|
* shows it. Callers dedupe on write (the SQL PK does it for real). */
|
|
102
|
-
export declare function pageRefEdges(
|
|
103
|
-
/** The publish-integrity gate (DESIGN.md §3): a page must not
|
|
104
|
-
* showing entries the published site
|
|
105
|
-
* entry must exist
|
|
106
|
-
|
|
126
|
+
export declare function pageRefEdges(trees: PageTree[], sections: SectionLike[]): RefEdge[];
|
|
127
|
+
/** The publish-integrity gate (DESIGN.md §3), per locale: a page must not
|
|
128
|
+
* go live in a locale showing entries the published site cannot resolve
|
|
129
|
+
* THERE — every referenced entry must exist, have a row in that locale,
|
|
130
|
+
* and be published in it. Throws naming the offenders. */
|
|
131
|
+
export declare function assertPublishable(label: string, tree: PageTree, sections: SectionLike[], entries: EntryStore, locale: string): Promise<void>;
|
|
107
132
|
export declare class MemoryPageStore implements PageStore {
|
|
108
133
|
private options;
|
|
109
134
|
private pages;
|
|
@@ -118,18 +143,27 @@ export declare class MemoryPageStore implements PageStore {
|
|
|
118
143
|
private byId;
|
|
119
144
|
private must;
|
|
120
145
|
private isHome;
|
|
146
|
+
private snapshot;
|
|
147
|
+
private children;
|
|
121
148
|
private hasChildren;
|
|
122
149
|
private siblings;
|
|
150
|
+
/** Sibling slugs are unique per parent PER LOCALE — a friendlier error
|
|
151
|
+
* than the path index's, which stays the real invariant. */
|
|
123
152
|
private assertSlugFree;
|
|
124
153
|
private subtree;
|
|
125
154
|
private writePaths;
|
|
155
|
+
/** Every locale's draft and published trees, deduped by version id. */
|
|
156
|
+
private liveTrees;
|
|
126
157
|
private writeRefs;
|
|
127
158
|
private newVersion;
|
|
159
|
+
private dropVersions;
|
|
128
160
|
createPage(input: CreatePageInput): Promise<PageRecord>;
|
|
129
161
|
getPage(id: string): Promise<PageRecord | null>;
|
|
130
162
|
listPages(): Promise<PageRecord[]>;
|
|
131
163
|
resolvePath(locale: string, path: string): Promise<PathTarget | null>;
|
|
132
164
|
pathsOf(id: string): Promise<Record<string, string>>;
|
|
165
|
+
addLocale(id: string, locale: string, options: AddLocaleOptions): Promise<PageRecord>;
|
|
166
|
+
removeLocale(id: string, locale: string): Promise<PageRecord>;
|
|
133
167
|
rename(id: string, patch: {
|
|
134
168
|
locale: string;
|
|
135
169
|
slug?: string;
|
|
@@ -140,10 +174,10 @@ export declare class MemoryPageStore implements PageStore {
|
|
|
140
174
|
index?: number;
|
|
141
175
|
}): Promise<PageRecord>;
|
|
142
176
|
setTemplate(id: string, template: string): Promise<PageRecord>;
|
|
143
|
-
saveDraft(id: string, tree: PageTree): Promise<PageVersion>;
|
|
144
|
-
publish(id: string): Promise<PageRecord>;
|
|
145
|
-
getDraftTree(id: string): Promise<PageTree | null>;
|
|
146
|
-
getPublishedTree(id: string): Promise<PageTree | null>;
|
|
147
|
-
listVersions(id: string): Promise<PageVersion[]>;
|
|
177
|
+
saveDraft(id: string, locale: string, tree: PageTree): Promise<PageVersion>;
|
|
178
|
+
publish(id: string, locale: string): Promise<PageRecord>;
|
|
179
|
+
getDraftTree(id: string, locale: string): Promise<PageTree | null>;
|
|
180
|
+
getPublishedTree(id: string, locale: string): Promise<PageTree | null>;
|
|
181
|
+
listVersions(id: string, locale: string): Promise<PageVersion[]>;
|
|
148
182
|
deletePage(id: string): Promise<void>;
|
|
149
183
|
}
|