@marktiderman/genesis-sites 0.0.0-stage → 0.2.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +465 -2
  3. package/dist/assets/form-island.c0f6fac859.js +51 -0
  4. package/dist/assets/islands.77d11f5697.js +1 -0
  5. package/dist/assets/sites-forms.4e6aa42497.css +10 -0
  6. package/dist/assets/sites-forms.css +10 -0
  7. package/dist/assets/sites.css +2 -0
  8. package/dist/assets/sites.e1ffd2708b.css +2 -0
  9. package/dist/blocks.d.ts +13 -0
  10. package/dist/blocks.js +26 -0
  11. package/dist/blocks.js.map +1 -0
  12. package/dist/chunk-3VKTBE7S.js +69 -0
  13. package/dist/chunk-3VKTBE7S.js.map +1 -0
  14. package/dist/chunk-C375ZPU2.js +2241 -0
  15. package/dist/chunk-C375ZPU2.js.map +1 -0
  16. package/dist/chunk-IXMY4B56.js +877 -0
  17. package/dist/chunk-IXMY4B56.js.map +1 -0
  18. package/dist/chunk-VZUDRPFG.js +13 -0
  19. package/dist/chunk-VZUDRPFG.js.map +1 -0
  20. package/dist/chunk-ZGWGQGT2.js +595 -0
  21. package/dist/chunk-ZGWGQGT2.js.map +1 -0
  22. package/dist/import.d.ts +130 -0
  23. package/dist/import.js +330 -0
  24. package/dist/import.js.map +1 -0
  25. package/dist/index.d.ts +273 -0
  26. package/dist/index.js +254 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/registry-D_DCWRTc.d.ts +1030 -0
  29. package/dist/sections.d.ts +802 -0
  30. package/dist/sections.js +6 -0
  31. package/dist/sections.js.map +1 -0
  32. package/dist/store-DkSycDhq.d.ts +443 -0
  33. package/dist/supabase.d.ts +41 -0
  34. package/dist/supabase.js +283 -0
  35. package/dist/supabase.js.map +1 -0
  36. package/dist/web.d.ts +136 -0
  37. package/dist/web.js +479 -0
  38. package/dist/web.js.map +1 -0
  39. package/migrations/0001_genesis_sites.sql +821 -0
  40. package/migrations/0002_genesis_sites_posts.sql +404 -0
  41. package/migrations/tests/0001_genesis_sites.test.sql +349 -0
  42. package/migrations/tests/0002_genesis_sites_posts.test.sql +242 -0
  43. package/package.json +152 -3
@@ -0,0 +1,283 @@
1
+ import { prepareImportPost, checkPost, isPublishable, PublishBlockedError, assetPath, ASSET_TYPES, prepareRelease } from './chunk-IXMY4B56.js';
2
+ export { ASSET_TYPES } from './chunk-IXMY4B56.js';
3
+ import { MAX_IMPORT_POSTS, validatePostSlug, PostDoc, PostSummarySchema, PageDoc, SiteDoc, parseSnapshot, ReleaseSnapshot } from './chunk-ZGWGQGT2.js';
4
+ import './chunk-3VKTBE7S.js';
5
+
6
+ // src/supabase/index.ts
7
+ var SITES_SCHEMA = "genesis_sites";
8
+ var SiteStoreError = class extends Error {
9
+ constructor(message, code, detail) {
10
+ super(message);
11
+ this.code = code;
12
+ this.detail = detail;
13
+ this.name = "SiteStoreError";
14
+ }
15
+ code;
16
+ detail;
17
+ };
18
+ function fail(error, what) {
19
+ throw new SiteStoreError(`${what}: ${error.message}`, error.code, error.details);
20
+ }
21
+ var metaOf = (r) => ({ id: r.id, siteId: r.siteId, number: r.number, note: r.note, createdAt: r.createdAt, publishedAt: r.publishedAt, brandHash: r.brandHash });
22
+ var fromRow = (r) => ({
23
+ id: r.id,
24
+ siteId: r.site_id,
25
+ number: r.number,
26
+ note: r.note,
27
+ createdAt: r.created_at,
28
+ publishedAt: r.published_at,
29
+ brandHash: r.brand_hash,
30
+ ...r.snapshot !== void 0 ? { snapshot: r.snapshot } : {}
31
+ });
32
+ var pageMeta = (r) => ({ id: r.id, siteId: r.site_id, path: r.path, kind: r.kind, title: r.title ?? "", sort: r.sort, rev: r.draft_rev });
33
+ var POST_META = "id, site_id, slug, status, draft_rev, published_at, updated_at, title:draft->>title";
34
+ var postMeta = (r) => ({ id: r.id, siteId: r.site_id, slug: r.slug, title: r.title ?? "", status: r.status, rev: r.draft_rev, publishedAt: r.published_at, updatedAt: r.updated_at });
35
+ var fromPostJson = (j) => ({
36
+ id: j.id,
37
+ siteId: j.siteId,
38
+ slug: j.slug,
39
+ title: j.title ?? "",
40
+ status: j.status,
41
+ rev: j.rev,
42
+ publishedAt: j.publishedAt,
43
+ updatedAt: j.updatedAt
44
+ });
45
+ function createSupabaseSiteStore(client, options = {}) {
46
+ const db = client.schema(SITES_SCHEMA);
47
+ const bucket = options.bucket ?? "site-assets";
48
+ async function release(id) {
49
+ const { data, error } = await db.from("site_releases").select("id, site_id, number, note, created_at, published_at, brand_hash, snapshot").eq("id", id).single();
50
+ if (error) fail(error, "reading the release");
51
+ const r = fromRow(data);
52
+ return { ...metaOf(r), snapshot: ReleaseSnapshot.parse(r.snapshot) };
53
+ }
54
+ async function pageRows(siteId, withDraft) {
55
+ const columns = withDraft ? "id, site_id, path, kind, sort, draft_rev, draft" : "id, site_id, path, kind, sort, draft_rev, title:draft->root->props->>title";
56
+ const { data, error } = await db.from("site_pages").select(columns).eq("site_id", siteId).is("deleted_at", null).order("sort").order("path");
57
+ if (error) fail(error, "listing pages");
58
+ return data ?? [];
59
+ }
60
+ const store = {
61
+ async getLive(siteId) {
62
+ const { data, error } = await db.rpc("live_release", { site_id: siteId });
63
+ if (error) fail(error, "reading the live site");
64
+ if (!data) return null;
65
+ const r = data;
66
+ const snapshot = parseSnapshot(r.snapshot, (m) => console.error(`[genesis-sites] the live release of site ${siteId}: ${m}`));
67
+ if (!snapshot) return null;
68
+ return { ...metaOf(r), snapshot };
69
+ },
70
+ async getDraft(siteId) {
71
+ const { data, error } = await db.from("sites").select("draft, draft_rev").eq("id", siteId).maybeSingle();
72
+ if (error) fail(error, "reading the site");
73
+ if (!data) return null;
74
+ return { site: SiteDoc.parse(data.draft), rev: data.draft_rev };
75
+ },
76
+ async saveSiteDraft(siteId, rev, site) {
77
+ const doc = SiteDoc.parse(site);
78
+ const { data, error } = await db.rpc("save_site_draft", { site_id: siteId, expected_rev: rev, doc });
79
+ if (error?.code === "GS409") {
80
+ const current = await store.getDraft(siteId);
81
+ if (!current) fail(error, "saving the site");
82
+ return { conflict: current.site, rev: current.rev };
83
+ }
84
+ if (error) fail(error, "saving the site");
85
+ return { rev: data };
86
+ },
87
+ async listPages(siteId) {
88
+ return (await pageRows(siteId, false)).map(pageMeta);
89
+ },
90
+ async getPageDraft(pageId) {
91
+ const { data, error } = await db.from("site_pages").select("id, site_id, path, kind, draft, draft_rev").eq("id", pageId).is("deleted_at", null).maybeSingle();
92
+ if (error) fail(error, "reading the page");
93
+ if (!data) return null;
94
+ const row = data;
95
+ return { doc: PageDoc.parse(row.draft), rev: row.draft_rev, siteId: row.site_id, path: row.path, kind: row.kind };
96
+ },
97
+ async savePageDraft(pageId, rev, doc) {
98
+ const parsed = PageDoc.parse(doc);
99
+ const { data, error } = await db.rpc("save_page_draft", { page_id: pageId, expected_rev: rev, doc: parsed });
100
+ if (error?.code === "GS409") {
101
+ const current = await store.getPageDraft(pageId);
102
+ if (!current) fail(error, "saving the page");
103
+ return { conflict: current.doc, rev: current.rev };
104
+ }
105
+ if (error) fail(error, "saving the page");
106
+ return { rev: data };
107
+ },
108
+ async createPage(siteId, page) {
109
+ const doc = PageDoc.parse(page.doc);
110
+ const { data, error } = await db.rpc("create_page", { site_id: siteId, path: page.path, kind: page.kind ?? "page", doc });
111
+ if (error) fail(error, "creating the page");
112
+ const meta = (await store.listPages(siteId)).find((p) => p.id === data);
113
+ if (!meta) throw new SiteStoreError("The page was created but can't be read back.", void 0);
114
+ return meta;
115
+ },
116
+ async movePage(pageId, path) {
117
+ const { error } = await db.rpc("move_page", { page_id: pageId, new_path: path });
118
+ if (error) fail(error, "moving the page");
119
+ const draft = await store.getPageDraft(pageId);
120
+ if (!draft) throw new SiteStoreError("The page moved but can't be read back.", void 0);
121
+ const meta = (await store.listPages(draft.siteId)).find((p) => p.id === pageId);
122
+ return meta;
123
+ },
124
+ async deletePage(pageId) {
125
+ const { error } = await db.rpc("delete_page", { page_id: pageId });
126
+ if (error) fail(error, "deleting the page");
127
+ },
128
+ async publish(siteId, opts = {}) {
129
+ const draft = await store.getDraft(siteId);
130
+ if (!draft) throw new SiteStoreError(`No site ${siteId} (or you can't see it).`, "P0002");
131
+ const rows = await pageRows(siteId, true);
132
+ const pages = rows.map((r) => ({ id: r.id, path: r.path, kind: r.kind, doc: PageDoc.parse(r.draft), rev: r.draft_rev }));
133
+ const prepared = prepareRelease(draft.site, pages, opts);
134
+ const revs = Object.fromEntries(pages.map((p) => [p.id, p.rev]));
135
+ let siteRev = draft.rev;
136
+ for (const m of prepared.migratedPages) {
137
+ const saved = await store.savePageDraft(m.id, revs[m.id], m.doc);
138
+ if ("conflict" in saved) throw new SiteStoreError("A page changed while publishing; check again.", "GS409");
139
+ revs[m.id] = saved.rev;
140
+ }
141
+ if (prepared.migratedSite) {
142
+ const saved = await store.saveSiteDraft(siteId, siteRev, prepared.migratedSite);
143
+ if ("conflict" in saved) throw new SiteStoreError("The site changed while publishing; check again.", "GS409");
144
+ siteRev = saved.rev;
145
+ }
146
+ const { data, error } = await db.rpc("publish_site", {
147
+ site_id: siteId,
148
+ expected_site_rev: siteRev,
149
+ expected_pages: revs,
150
+ note: opts.note ?? null,
151
+ brand_hash: prepared.brandHash
152
+ });
153
+ if (error) fail(error, "publishing");
154
+ return release(data.id);
155
+ },
156
+ async rollback(siteId, releaseId) {
157
+ const { data, error } = await db.rpc("rollback_site", { site_id: siteId, release_id: releaseId });
158
+ if (error) fail(error, "rolling back");
159
+ return release(data.id);
160
+ },
161
+ async listReleases(siteId) {
162
+ const { data, error } = await db.from("site_releases").select("id, site_id, number, note, created_at, published_at, brand_hash").eq("site_id", siteId).order("number", { ascending: false });
163
+ if (error) fail(error, "listing releases");
164
+ return (data ?? []).map((r) => metaOf(fromRow(r)));
165
+ },
166
+ async putAsset(siteId, file, meta = {}) {
167
+ const mime = file.type || "application/octet-stream";
168
+ const path = assetPath(siteId, crypto.randomUUID(), meta.name, mime);
169
+ if (!ASSET_TYPES.includes(mime)) throw new SiteStoreError(`uploading: ${mime} isn't an image (PNG, JPEG, WebP, AVIF, GIF) or a PDF.`, "22023");
170
+ const upload = await client.storage.from(bucket).upload(path, file, { contentType: mime, upsert: false });
171
+ if (upload.error) throw new SiteStoreError(`uploading: ${upload.error.message}`, void 0);
172
+ const { data, error } = await db.from("site_assets").insert({ site_id: siteId, path, mime, alt: meta.alt ?? null, width: meta.width ?? null, height: meta.height ?? null }).select("id").single();
173
+ if (error) {
174
+ await client.storage.from(bucket).remove([path]);
175
+ fail(error, "recording the upload");
176
+ }
177
+ const url = client.storage.from(bucket).getPublicUrl(path).data.publicUrl;
178
+ return { id: data.id, siteId, path, url, mime, alt: meta.alt ?? null, width: meta.width ?? null, height: meta.height ?? null };
179
+ },
180
+ // ── Posts ──
181
+ async listLivePosts(siteId) {
182
+ const { data, error } = await db.rpc("live_posts", { site_id: siteId });
183
+ if (error) fail(error, "reading the posts");
184
+ const out = [];
185
+ for (const item of Array.isArray(data) ? data : []) {
186
+ const parsed = PostSummarySchema.safeParse(item);
187
+ if (parsed.success) out.push(parsed.data);
188
+ else console.error(`[genesis-sites] a post of site ${siteId} doesn't parse and is left out of the list: ${parsed.error.message}`);
189
+ }
190
+ return out;
191
+ },
192
+ async getLivePost(siteId, slug) {
193
+ const { data, error } = await db.rpc("live_post", { site_id: siteId, slug });
194
+ if (error) fail(error, "reading the post");
195
+ if (!data) return null;
196
+ const raw = data;
197
+ const doc = PostDoc.safeParse(raw.doc);
198
+ if (!doc.success) {
199
+ console.error(`[genesis-sites] the post ${JSON.stringify(slug)} of site ${siteId} doesn't parse and is left out: ${doc.error.message}`);
200
+ return null;
201
+ }
202
+ return { slug: raw.slug, publishedAt: raw.publishedAt, updatedAt: raw.updatedAt, doc: doc.data };
203
+ },
204
+ async listPosts(siteId) {
205
+ const { data, error } = await db.from("site_posts").select(POST_META).eq("site_id", siteId).is("deleted_at", null).order("published_at", { ascending: false, nullsFirst: true }).order("updated_at", { ascending: false });
206
+ if (error) fail(error, "listing posts");
207
+ return (data ?? []).map(postMeta);
208
+ },
209
+ async getPost(postId) {
210
+ const { data, error } = await db.from("site_posts").select(`${POST_META}, draft`).eq("id", postId).is("deleted_at", null).maybeSingle();
211
+ if (error) fail(error, "reading the post");
212
+ if (!data) return null;
213
+ const row = data;
214
+ const doc = PostDoc.safeParse(row.draft);
215
+ if (!doc.success) {
216
+ console.error(`[genesis-sites] the draft of post ${postId} doesn't parse: ${doc.error.message}`);
217
+ return null;
218
+ }
219
+ return { ...postMeta(row), doc: doc.data };
220
+ },
221
+ async savePost(siteId, input) {
222
+ validatePostSlug(input.slug);
223
+ const doc = PostDoc.parse(input.doc);
224
+ const { data, error } = await db.rpc("save_post", { site_id: siteId, post_id: input.id ?? null, slug: input.slug, expected_rev: input.rev ?? null, doc });
225
+ if (error?.code === "GS409" && input.id) {
226
+ const current = await store.getPost(input.id);
227
+ if (!current) fail(error, "saving the post");
228
+ return { id: input.id, conflict: current.doc, rev: current.rev };
229
+ }
230
+ if (error) fail(error, "saving the post");
231
+ const saved = data;
232
+ return { id: saved.id, rev: saved.rev };
233
+ },
234
+ async publishPost(postId) {
235
+ const draft = await store.getPost(postId);
236
+ if (!draft) throw new SiteStoreError(`No post ${postId} (or you can't see it).`, "P0002");
237
+ const issues = checkPost(draft.slug, draft.doc);
238
+ if (!isPublishable(issues)) throw new PublishBlockedError(issues);
239
+ const { data, error } = await db.rpc("publish_post", { post_id: postId, expected_rev: draft.rev });
240
+ if (error) fail(error, "publishing the post");
241
+ return fromPostJson(data);
242
+ },
243
+ async unpublishPost(postId) {
244
+ const { data, error } = await db.rpc("unpublish_post", { post_id: postId });
245
+ if (error) fail(error, "taking the post down");
246
+ return fromPostJson(data);
247
+ },
248
+ async deletePost(postId) {
249
+ const { data: row, error: readError } = await db.from("site_posts").select("site_id").eq("id", postId).is("deleted_at", null).maybeSingle();
250
+ if (readError) fail(readError, "reading the post");
251
+ const { error } = await db.rpc("delete_post", { post_id: postId });
252
+ if (error) fail(error, "deleting the post");
253
+ return row ? { siteId: row.site_id } : null;
254
+ },
255
+ async importPosts(siteId, list) {
256
+ if (list.length > MAX_IMPORT_POSTS) throw new SiteStoreError(`Import at most ${MAX_IMPORT_POSTS} posts at a time.`, "22023");
257
+ const result = { imported: [], skipped: [] };
258
+ const issues = /* @__PURE__ */ new Map();
259
+ const rows = [];
260
+ for (const item of list) {
261
+ const prepared = prepareImportPost(item);
262
+ if ("reason" in prepared) result.skipped.push(prepared);
263
+ else {
264
+ rows.push({ slug: item.slug, doc: prepared.doc, status: prepared.status, publishedAt: prepared.publishedAt });
265
+ if (prepared.issues.length) issues.set(item.slug, prepared.issues);
266
+ }
267
+ }
268
+ if (rows.length) {
269
+ const { data, error } = await db.rpc("import_posts", { site_id: siteId, posts: rows });
270
+ if (error) fail(error, "importing posts");
271
+ const out = data;
272
+ for (const imported of out.imported) result.imported.push({ ...imported, ...issues.has(imported.slug) ? { issues: issues.get(imported.slug) } : {} });
273
+ result.skipped.push(...out.skipped);
274
+ }
275
+ return result;
276
+ }
277
+ };
278
+ return store;
279
+ }
280
+
281
+ export { SITES_SCHEMA, SiteStoreError, createSupabaseSiteStore };
282
+ //# sourceMappingURL=supabase.js.map
283
+ //# sourceMappingURL=supabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/supabase/index.ts"],"names":[],"mappings":"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","file":"supabase.js","sourcesContent":["/**\n * @marktiderman/genesis-sites/supabase: `createSupabaseSiteStore`, the\n * `SiteStore` over the `genesis_sites` schema (migration\n * `migrations/0001_genesis_sites.sql`).\n *\n * Pass a client signed in as the editor for drafts and publishing (row level\n * security and `can_edit` / `can_view` decide), or an anonymous one for the\n * public read (`getLive`, through `genesis_sites.live_release`). The schema\n * must be exposed to the API.\n *\n * @packageDocumentation\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport { checkPost, isPublishable } from \"../core/a11y\";\nimport { MAX_IMPORT_POSTS, PostDoc, PostSummarySchema, validatePostSlug, type ImportPostsResult, type LivePost, type PostMeta, type PostStatus, type PostSummary } from \"../core/posts\";\nimport { PageDoc, ReleaseSnapshot, SiteDoc, parseSnapshot, type PageKind, type PageMeta, type Release, type ReleaseMeta } from \"../core/schemas\";\nimport { ASSET_TYPES, PublishBlockedError, assetPath, prepareImportPost, prepareRelease, type Asset, type AssetMeta, type PublishOptions, type SaveResult, type SiteStore } from \"../core/store\";\n\nexport const SITES_SCHEMA = \"genesis_sites\";\n\n/** The file types a site asset may be, as the database checks them. */\nexport { ASSET_TYPES };\n\nexport interface SupabaseSiteStoreOptions {\n /** The storage bucket for `putAsset`. Default `site-assets`. */\n bucket?: string;\n}\n\n/** A database error with its SQLSTATE (`GS409` a conflict, `42501` not allowed). */\nexport class SiteStoreError extends Error {\n constructor(\n message: string,\n readonly code: string | undefined,\n readonly detail?: string,\n ) {\n super(message);\n this.name = \"SiteStoreError\";\n }\n}\n\ninterface PgError {\n message: string;\n code?: string;\n details?: string;\n}\n\nfunction fail(error: PgError, what: string): never {\n throw new SiteStoreError(`${what}: ${error.message}`, error.code, error.details);\n}\n\ninterface ReleaseJson {\n id: string;\n siteId: string;\n number: number;\n note: string | null;\n createdAt: string;\n publishedAt: string | null;\n brandHash: string | null;\n snapshot?: unknown;\n}\n\nconst metaOf = (r: ReleaseJson): ReleaseMeta => ({ id: r.id, siteId: r.siteId, number: r.number, note: r.note, createdAt: r.createdAt, publishedAt: r.publishedAt, brandHash: r.brandHash });\n\ninterface ReleaseRow {\n id: string;\n site_id: string;\n number: number;\n note: string | null;\n created_at: string;\n published_at: string | null;\n brand_hash: string | null;\n snapshot?: unknown;\n}\n\nconst fromRow = (r: ReleaseRow): ReleaseJson => ({\n id: r.id,\n siteId: r.site_id,\n number: r.number,\n note: r.note,\n createdAt: r.created_at,\n publishedAt: r.published_at,\n brandHash: r.brand_hash,\n ...(r.snapshot !== undefined ? { snapshot: r.snapshot } : {}),\n});\n\ninterface PageRow {\n id: string;\n site_id: string;\n path: string;\n kind: PageKind;\n sort: number;\n draft_rev: number;\n title?: string | null;\n draft?: unknown;\n}\n\nconst pageMeta = (r: PageRow): PageMeta => ({ id: r.id, siteId: r.site_id, path: r.path, kind: r.kind, title: r.title ?? \"\", sort: r.sort, rev: r.draft_rev });\n\ninterface PostRow {\n id: string;\n site_id: string;\n slug: string;\n status: PostStatus;\n draft_rev: number;\n published_at: string | null;\n updated_at: string;\n title?: string | null;\n draft?: unknown;\n}\n\nconst POST_META = \"id, site_id, slug, status, draft_rev, published_at, updated_at, title:draft->>title\";\nconst postMeta = (r: PostRow): PostMeta => ({ id: r.id, siteId: r.site_id, slug: r.slug, title: r.title ?? \"\", status: r.status, rev: r.draft_rev, publishedAt: r.published_at, updatedAt: r.updated_at });\nconst fromPostJson = (j: { id: string; siteId: string; slug: string; title: string | null; status: PostStatus; rev: number; publishedAt: string | null; updatedAt: string }): PostMeta => ({\n id: j.id,\n siteId: j.siteId,\n slug: j.slug,\n title: j.title ?? \"\",\n status: j.status,\n rev: j.rev,\n publishedAt: j.publishedAt,\n updatedAt: j.updatedAt,\n});\n\n/**\n * A `SiteStore` on Supabase.\n *\n * @stability Beta\n */\nexport function createSupabaseSiteStore(client: SupabaseClient, options: SupabaseSiteStoreOptions = {}): SiteStore {\n const db = client.schema(SITES_SCHEMA);\n const bucket = options.bucket ?? \"site-assets\";\n\n async function release(id: string): Promise<Release> {\n const { data, error } = await db.from(\"site_releases\").select(\"id, site_id, number, note, created_at, published_at, brand_hash, snapshot\").eq(\"id\", id).single();\n if (error) fail(error, \"reading the release\");\n const r = fromRow(data as ReleaseRow);\n return { ...metaOf(r), snapshot: ReleaseSnapshot.parse(r.snapshot) };\n }\n\n async function pageRows(siteId: string, withDraft: boolean): Promise<PageRow[]> {\n const columns = withDraft ? \"id, site_id, path, kind, sort, draft_rev, draft\" : \"id, site_id, path, kind, sort, draft_rev, title:draft->root->props->>title\";\n const { data, error } = await db.from(\"site_pages\").select(columns).eq(\"site_id\", siteId).is(\"deleted_at\", null).order(\"sort\").order(\"path\");\n if (error) fail(error, \"listing pages\");\n return (data ?? []) as unknown as PageRow[];\n }\n\n const store: SiteStore = {\n async getLive(siteId) {\n const { data, error } = await db.rpc(\"live_release\", { site_id: siteId });\n if (error) fail(error, \"reading the live site\");\n if (!data) return null;\n const r = data as ReleaseJson;\n // Page by page: a stored page that no longer parses is left out (and logged), not the whole site.\n const snapshot = parseSnapshot(r.snapshot, (m) => console.error(`[genesis-sites] the live release of site ${siteId}: ${m}`));\n if (!snapshot) return null;\n return { ...metaOf(r), snapshot };\n },\n async getDraft(siteId) {\n const { data, error } = await db.from(\"sites\").select(\"draft, draft_rev\").eq(\"id\", siteId).maybeSingle();\n if (error) fail(error, \"reading the site\");\n if (!data) return null;\n return { site: SiteDoc.parse((data as { draft: unknown }).draft), rev: (data as { draft_rev: number }).draft_rev };\n },\n async saveSiteDraft(siteId, rev, site): Promise<SaveResult<SiteDoc>> {\n const doc = SiteDoc.parse(site);\n const { data, error } = await db.rpc(\"save_site_draft\", { site_id: siteId, expected_rev: rev, doc });\n if (error?.code === \"GS409\") {\n const current = await store.getDraft(siteId);\n if (!current) fail(error, \"saving the site\");\n return { conflict: current.site, rev: current.rev };\n }\n if (error) fail(error, \"saving the site\");\n return { rev: data as number };\n },\n async listPages(siteId) {\n return (await pageRows(siteId, false)).map(pageMeta);\n },\n async getPageDraft(pageId) {\n const { data, error } = await db.from(\"site_pages\").select(\"id, site_id, path, kind, draft, draft_rev\").eq(\"id\", pageId).is(\"deleted_at\", null).maybeSingle();\n if (error) fail(error, \"reading the page\");\n if (!data) return null;\n const row = data as PageRow;\n return { doc: PageDoc.parse(row.draft), rev: row.draft_rev, siteId: row.site_id, path: row.path, kind: row.kind };\n },\n async savePageDraft(pageId, rev, doc): Promise<SaveResult<PageDoc>> {\n const parsed = PageDoc.parse(doc);\n const { data, error } = await db.rpc(\"save_page_draft\", { page_id: pageId, expected_rev: rev, doc: parsed });\n if (error?.code === \"GS409\") {\n const current = await store.getPageDraft(pageId);\n if (!current) fail(error, \"saving the page\");\n return { conflict: current.doc, rev: current.rev };\n }\n if (error) fail(error, \"saving the page\");\n return { rev: data as number };\n },\n async createPage(siteId, page) {\n const doc = PageDoc.parse(page.doc);\n const { data, error } = await db.rpc(\"create_page\", { site_id: siteId, path: page.path, kind: page.kind ?? \"page\", doc });\n if (error) fail(error, \"creating the page\");\n const meta = (await store.listPages(siteId)).find((p) => p.id === data);\n if (!meta) throw new SiteStoreError(\"The page was created but can't be read back.\", undefined);\n return meta;\n },\n async movePage(pageId, path) {\n const { error } = await db.rpc(\"move_page\", { page_id: pageId, new_path: path });\n if (error) fail(error, \"moving the page\");\n const draft = await store.getPageDraft(pageId);\n if (!draft) throw new SiteStoreError(\"The page moved but can't be read back.\", undefined);\n const meta = (await store.listPages(draft.siteId)).find((p) => p.id === pageId)!;\n return meta;\n },\n async deletePage(pageId) {\n const { error } = await db.rpc(\"delete_page\", { page_id: pageId });\n if (error) fail(error, \"deleting the page\");\n },\n async publish(siteId, opts: PublishOptions = {}) {\n const draft = await store.getDraft(siteId);\n if (!draft) throw new SiteStoreError(`No site ${siteId} (or you can't see it).`, \"P0002\");\n const rows = await pageRows(siteId, true);\n const pages = rows.map((r) => ({ id: r.id, path: r.path, kind: r.kind, doc: PageDoc.parse(r.draft), rev: r.draft_rev }));\n // Throws PublishBlockedError when a check fails.\n const prepared = prepareRelease(draft.site, pages, opts);\n // Migrated props go back into the drafts first, so the release holds them.\n const revs: Record<string, number> = Object.fromEntries(pages.map((p) => [p.id, p.rev]));\n let siteRev = draft.rev;\n for (const m of prepared.migratedPages) {\n const saved = await store.savePageDraft(m.id, revs[m.id]!, m.doc);\n if (\"conflict\" in saved) throw new SiteStoreError(\"A page changed while publishing; check again.\", \"GS409\");\n revs[m.id] = saved.rev;\n }\n if (prepared.migratedSite) {\n const saved = await store.saveSiteDraft(siteId, siteRev, prepared.migratedSite);\n if (\"conflict\" in saved) throw new SiteStoreError(\"The site changed while publishing; check again.\", \"GS409\");\n siteRev = saved.rev;\n }\n const { data, error } = await db.rpc(\"publish_site\", {\n site_id: siteId,\n expected_site_rev: siteRev,\n expected_pages: revs,\n note: opts.note ?? null,\n brand_hash: prepared.brandHash,\n });\n if (error) fail(error, \"publishing\");\n return release((data as ReleaseJson).id);\n },\n async rollback(siteId, releaseId) {\n const { data, error } = await db.rpc(\"rollback_site\", { site_id: siteId, release_id: releaseId });\n if (error) fail(error, \"rolling back\");\n return release((data as ReleaseJson).id);\n },\n async listReleases(siteId) {\n const { data, error } = await db.from(\"site_releases\").select(\"id, site_id, number, note, created_at, published_at, brand_hash\").eq(\"site_id\", siteId).order(\"number\", { ascending: false });\n if (error) fail(error, \"listing releases\");\n return ((data ?? []) as ReleaseRow[]).map((r) => metaOf(fromRow(r)));\n },\n async putAsset(siteId, file: Blob, meta: AssetMeta = {}): Promise<Asset> {\n const mime = file.type || \"application/octet-stream\";\n // Images under sites/<id>/, files to download (a PDF) under sites/<id>/files/.\n const path = assetPath(siteId, crypto.randomUUID(), meta.name, mime);\n // The types the database records (no SVG or HTML: a browser runs them as pages). Checked before the upload,\n // so a refused file never reaches the bucket.\n if (!ASSET_TYPES.includes(mime)) throw new SiteStoreError(`uploading: ${mime} isn't an image (PNG, JPEG, WebP, AVIF, GIF) or a PDF.`, \"22023\");\n const upload = await client.storage.from(bucket).upload(path, file, { contentType: mime, upsert: false });\n if (upload.error) throw new SiteStoreError(`uploading: ${upload.error.message}`, undefined);\n const { data, error } = await db\n .from(\"site_assets\")\n .insert({ site_id: siteId, path, mime, alt: meta.alt ?? null, width: meta.width ?? null, height: meta.height ?? null })\n .select(\"id\")\n .single();\n if (error) {\n await client.storage.from(bucket).remove([path]);\n fail(error, \"recording the upload\");\n }\n const url = client.storage.from(bucket).getPublicUrl(path).data.publicUrl;\n return { id: (data as { id: string }).id, siteId, path, url, mime, alt: meta.alt ?? null, width: meta.width ?? null, height: meta.height ?? null };\n },\n\n // ── Posts ──\n async listLivePosts(siteId): Promise<PostSummary[]> {\n const { data, error } = await db.rpc(\"live_posts\", { site_id: siteId });\n if (error) fail(error, \"reading the posts\");\n // Item by item, as a page is: a stored post that no longer parses is left out and logged.\n const out: PostSummary[] = [];\n for (const item of Array.isArray(data) ? data : []) {\n const parsed = PostSummarySchema.safeParse(item);\n if (parsed.success) out.push(parsed.data as PostSummary);\n else console.error(`[genesis-sites] a post of site ${siteId} doesn't parse and is left out of the list: ${parsed.error.message}`);\n }\n return out;\n },\n async getLivePost(siteId, slug): Promise<LivePost | null> {\n const { data, error } = await db.rpc(\"live_post\", { site_id: siteId, slug });\n if (error) fail(error, \"reading the post\");\n if (!data) return null;\n const raw = data as { slug: string; publishedAt: string; updatedAt: string; doc: unknown };\n // A stored post that no longer parses is left out (and logged), as a page would be.\n const doc = PostDoc.safeParse(raw.doc);\n if (!doc.success) {\n console.error(`[genesis-sites] the post ${JSON.stringify(slug)} of site ${siteId} doesn't parse and is left out: ${doc.error.message}`);\n return null;\n }\n return { slug: raw.slug, publishedAt: raw.publishedAt, updatedAt: raw.updatedAt, doc: doc.data };\n },\n async listPosts(siteId) {\n const { data, error } = await db.from(\"site_posts\").select(POST_META).eq(\"site_id\", siteId).is(\"deleted_at\", null).order(\"published_at\", { ascending: false, nullsFirst: true }).order(\"updated_at\", { ascending: false });\n if (error) fail(error, \"listing posts\");\n return ((data ?? []) as unknown as PostRow[]).map(postMeta);\n },\n async getPost(postId) {\n const { data, error } = await db.from(\"site_posts\").select(`${POST_META}, draft`).eq(\"id\", postId).is(\"deleted_at\", null).maybeSingle();\n if (error) fail(error, \"reading the post\");\n if (!data) return null;\n const row = data as unknown as PostRow;\n const doc = PostDoc.safeParse(row.draft);\n if (!doc.success) {\n console.error(`[genesis-sites] the draft of post ${postId} doesn't parse: ${doc.error.message}`);\n return null;\n }\n return { ...postMeta(row), doc: doc.data };\n },\n async savePost(siteId, input) {\n validatePostSlug(input.slug);\n const doc = PostDoc.parse(input.doc);\n const { data, error } = await db.rpc(\"save_post\", { site_id: siteId, post_id: input.id ?? null, slug: input.slug, expected_rev: input.rev ?? null, doc });\n if (error?.code === \"GS409\" && input.id) {\n const current = await store.getPost!(input.id);\n if (!current) fail(error, \"saving the post\");\n return { id: input.id, conflict: current.doc, rev: current.rev };\n }\n if (error) fail(error, \"saving the post\");\n const saved = data as { id: string; rev: number };\n return { id: saved.id, rev: saved.rev };\n },\n async publishPost(postId) {\n const draft = await store.getPost!(postId);\n if (!draft) throw new SiteStoreError(`No post ${postId} (or you can't see it).`, \"P0002\");\n const issues = checkPost(draft.slug, draft.doc);\n if (!isPublishable(issues)) throw new PublishBlockedError(issues);\n // The revision just checked: if someone saved since, the database refuses (GS409).\n const { data, error } = await db.rpc(\"publish_post\", { post_id: postId, expected_rev: draft.rev });\n if (error) fail(error, \"publishing the post\");\n return fromPostJson(data as Parameters<typeof fromPostJson>[0]);\n },\n async unpublishPost(postId) {\n const { data, error } = await db.rpc(\"unpublish_post\", { post_id: postId });\n if (error) fail(error, \"taking the post down\");\n return fromPostJson(data as Parameters<typeof fromPostJson>[0]);\n },\n async deletePost(postId) {\n // The site, read as the editor (RLS), before the row is marked deleted.\n const { data: row, error: readError } = await db.from(\"site_posts\").select(\"site_id\").eq(\"id\", postId).is(\"deleted_at\", null).maybeSingle();\n if (readError) fail(readError, \"reading the post\");\n const { error } = await db.rpc(\"delete_post\", { post_id: postId });\n if (error) fail(error, \"deleting the post\");\n return row ? { siteId: (row as { site_id: string }).site_id } : null;\n },\n async importPosts(siteId, list): Promise<ImportPostsResult> {\n if (list.length > MAX_IMPORT_POSTS) throw new SiteStoreError(`Import at most ${MAX_IMPORT_POSTS} posts at a time.`, \"22023\");\n // The checks run here; a post that fails one is imported as a draft, with its issues.\n const result: ImportPostsResult = { imported: [], skipped: [] };\n const issues = new Map<string, string[]>();\n const rows: { slug: string; doc: PostDoc; status: PostStatus; publishedAt: string }[] = [];\n for (const item of list) {\n const prepared = prepareImportPost(item);\n if (\"reason\" in prepared) result.skipped.push(prepared);\n else {\n rows.push({ slug: item.slug, doc: prepared.doc, status: prepared.status, publishedAt: prepared.publishedAt });\n if (prepared.issues.length) issues.set(item.slug, prepared.issues);\n }\n }\n if (rows.length) {\n const { data, error } = await db.rpc(\"import_posts\", { site_id: siteId, posts: rows });\n if (error) fail(error, \"importing posts\");\n const out = data as ImportPostsResult;\n for (const imported of out.imported) result.imported.push({ ...imported, ...(issues.has(imported.slug) ? { issues: issues.get(imported.slug)! } : {}) });\n result.skipped.push(...out.skipped);\n }\n return result;\n },\n };\n return store;\n}\n"]}
package/dist/web.d.ts ADDED
@@ -0,0 +1,136 @@
1
+ import { i as FormsResolver, N as ReleaseSnapshot, R as ReleasePage, Q as ResolvedForm, E as PostsContext } from './registry-D_DCWRTc.js';
2
+ import { S as SiteStore, d as BrandSource, R as ReservedPaths, e as FontInfo, r as SiteTheme } from './store-DkSycDhq.js';
3
+ import * as React from 'react';
4
+ import 'zod';
5
+ import '@marktiderman/genesis-design-system';
6
+
7
+ /** Where this package serves its own files, under the site's base path. Reserved for pages. */
8
+ declare const ASSETS_PREFIX = "/_gs";
9
+ interface SiteRequestOptions {
10
+ /**
11
+ * The live path needs only `getLive` (and, for a blog, `listLivePosts` and
12
+ * `getLivePost`); preview needs the draft reads too (`listPosts` and
13
+ * `getPost` for posts).
14
+ */
15
+ store: Pick<SiteStore, "getLive"> & Partial<Pick<SiteStore, "getDraft" | "listPages" | "getPageDraft" | "listLivePosts" | "getLivePost" | "listPosts" | "getPost">>;
16
+ brand: BrandSource;
17
+ /** The site this request is for, resolved by the host from the Host header (or a path). */
18
+ siteId: string;
19
+ /**
20
+ * `live` (default) serves the published release, cached at the CDN.
21
+ * `preview` serves the drafts, `noindex` and `no-store`; the store must be
22
+ * one signed in as the viewer, so the database's `can_view` decides who sees them.
23
+ * Preview also needs `viewer`: without one it answers null before reading a draft.
24
+ */
25
+ mode?: "live" | "preview";
26
+ /**
27
+ * Preview only: the signed-in viewer's user id, as the host's session
28
+ * resolved it. Drafts are never read for an anonymous request (anon has no
29
+ * grant on them), so a preview without a viewer is null: the host decides
30
+ * what to show (its sign-in page, say). Live ignores it.
31
+ */
32
+ viewer?: string | null;
33
+ /**
34
+ * The site's primary origin (`https://riverside.example`) for canonical,
35
+ * Open Graph and sitemap URLs. Default: the request URL's origin. Pass it
36
+ * whenever a site has more than one hostname, so every alias names the same
37
+ * canonical address.
38
+ */
39
+ origin?: string;
40
+ /** The path the site is mounted under (`/sites-demo`); `""` at the root. */
41
+ basePath?: string;
42
+ /** Paths that belong to the host app; null for them. Default `DEFAULT_RESERVED_PATHS`. */
43
+ reserved?: ReservedPaths;
44
+ /** Resolves a form embed's slug. Without it, form embeds render nothing (and log). */
45
+ forms?: FormsResolver;
46
+ /** Called with the cache tags of every cacheable response, for hosts that set their own cache headers. */
47
+ onPublishedTags?: (tags: string[], info: {
48
+ siteId: string;
49
+ releaseId: string | null;
50
+ path: string;
51
+ status: number;
52
+ }) => void;
53
+ /** An unknown path: `fall-through` (default) returns null; `not-found` serves the site's 404 page. */
54
+ unknownPath?: "fall-through" | "not-found";
55
+ /** Font stylesheets: Google Fonts by default, `false` for none, or a function returning a URL per font. */
56
+ fonts?: false | ((font: FontInfo) => string | null);
57
+ /** A Content-Security-Policy header (default true). */
58
+ csp?: boolean;
59
+ }
60
+ /**
61
+ * Serve a site request, or return null when the request isn't the site's.
62
+ *
63
+ * @stability Beta
64
+ */
65
+ declare function handleSiteRequest(request: Request, options: SiteRequestOptions): Promise<Response | null>;
66
+ /**
67
+ * One page as an HTML string (tests, static export, the editor's preview frame).
68
+ *
69
+ * @stability Beta
70
+ */
71
+ declare function renderSitePageToString(request: Request, options: SiteRequestOptions): Promise<{
72
+ status: number;
73
+ html: string;
74
+ headers: Headers;
75
+ } | null>;
76
+
77
+ /**
78
+ * SiteRenderer: one page of a release as a whole HTML document. Server-only
79
+ * output: it is rendered to a string or stream and never hydrated. The only
80
+ * script is the islands loader, and only on a page with a phone menu or a
81
+ * form.
82
+ */
83
+
84
+ interface SiteAssets {
85
+ /** The shared stylesheet. */
86
+ css: string;
87
+ /** The form stylesheet, linked only on pages with a form. */
88
+ formsCss: string;
89
+ /** The islands loader. */
90
+ islands: string;
91
+ }
92
+ interface SiteRendererProps {
93
+ snapshot: ReleaseSnapshot;
94
+ /** The page to render, or null for the site's "not found" page. */
95
+ page: ReleasePage | null;
96
+ theme: SiteTheme;
97
+ /** The site's primary origin, for canonical and Open Graph URLs. */
98
+ origin: string;
99
+ /** The path prefix the site is served under (`""` at the root). */
100
+ basePath?: string;
101
+ mode?: "live" | "preview";
102
+ logo?: {
103
+ src: string;
104
+ alt: string;
105
+ };
106
+ /** Forms on the page, already resolved (see `collectFormSlugs`). */
107
+ forms?: ReadonlyMap<string, ResolvedForm | null>;
108
+ /** Asset URLs. */
109
+ assets: SiteAssets;
110
+ /** The stylesheet URL for a font, or null to rely on installed fonts. Default: Google Fonts. */
111
+ fontHref?: ((font: FontInfo) => string | null) | false;
112
+ /**
113
+ * The blog, when the site has a post page: its path, the posts, the post
114
+ * this page shows (`page` is then that post on the post page) and the
115
+ * list's page number.
116
+ */
117
+ posts?: PostsContext;
118
+ }
119
+ declare const googleFontHref: (font: FontInfo) => string | null;
120
+ /** The form slugs a page uses, to resolve before rendering: well-formed slugs of visible form embeds in the page body. */
121
+ declare function collectFormSlugs(_snapshot: ReleaseSnapshot, page: ReleasePage | null): string[];
122
+ /**
123
+ * The document for one page.
124
+ *
125
+ * @stability Beta
126
+ */
127
+ declare function SiteRenderer({ snapshot, page, theme, origin, basePath, mode, logo, forms, assets, fontHref, posts }: SiteRendererProps): React.JSX.Element;
128
+
129
+ declare const ASSET_SIZES: {
130
+ readonly css: 37194;
131
+ readonly formsCss: 74939;
132
+ readonly islands: 3010;
133
+ readonly formIsland: 502065;
134
+ };
135
+
136
+ export { ASSETS_PREFIX, ASSET_SIZES, type SiteAssets, SiteRenderer, type SiteRendererProps, type SiteRequestOptions, collectFormSlugs, googleFontHref, handleSiteRequest, renderSitePageToString };