@marktiderman/genesis-sites 0.1.0 → 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 (47) hide show
  1. package/README.md +152 -8
  2. package/dist/assets/islands.77d11f5697.js +1 -0
  3. package/dist/assets/sites-forms.4e6aa42497.css +10 -0
  4. package/dist/assets/sites-forms.css +1 -1
  5. package/dist/assets/sites.css +1 -1
  6. package/dist/assets/sites.e1ffd2708b.css +2 -0
  7. package/dist/blocks.d.ts +1 -1
  8. package/dist/blocks.js +5 -1
  9. package/dist/blocks.js.map +1 -1
  10. package/dist/chunk-3VKTBE7S.js +69 -0
  11. package/dist/chunk-3VKTBE7S.js.map +1 -0
  12. package/dist/chunk-C375ZPU2.js +2241 -0
  13. package/dist/chunk-C375ZPU2.js.map +1 -0
  14. package/dist/{chunk-GA7U3CIN.js → chunk-IXMY4B56.js} +229 -10
  15. package/dist/chunk-IXMY4B56.js.map +1 -0
  16. package/dist/chunk-VZUDRPFG.js +13 -0
  17. package/dist/chunk-VZUDRPFG.js.map +1 -0
  18. package/dist/{chunk-KEF3V5ZN.js → chunk-ZGWGQGT2.js} +169 -73
  19. package/dist/chunk-ZGWGQGT2.js.map +1 -0
  20. package/dist/import.d.ts +130 -0
  21. package/dist/import.js +330 -0
  22. package/dist/import.js.map +1 -0
  23. package/dist/index.d.ts +40 -7
  24. package/dist/index.js +5 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/{registry-CdgVymsu.d.ts → registry-D_DCWRTc.d.ts} +274 -8
  27. package/dist/sections.d.ts +422 -6
  28. package/dist/sections.js +4 -2
  29. package/dist/{store-BzGw2i5J.d.ts → store-DkSycDhq.d.ts} +119 -53
  30. package/dist/supabase.d.ts +5 -5
  31. package/dist/supabase.js +115 -6
  32. package/dist/supabase.js.map +1 -1
  33. package/dist/web.d.ts +26 -8
  34. package/dist/web.js +142 -18
  35. package/dist/web.js.map +1 -1
  36. package/migrations/0001_genesis_sites.sql +64 -6
  37. package/migrations/0002_genesis_sites_posts.sql +404 -0
  38. package/migrations/tests/0001_genesis_sites.test.sql +30 -1
  39. package/migrations/tests/0002_genesis_sites_posts.test.sql +242 -0
  40. package/package.json +18 -10
  41. package/dist/assets/islands.230b20b9c4.js +0 -1
  42. package/dist/assets/sites-forms.5e6ebf31c3.css +0 -10
  43. package/dist/assets/sites.b28b796c6f.css +0 -2
  44. package/dist/chunk-43XUAO63.js +0 -1255
  45. package/dist/chunk-43XUAO63.js.map +0 -1
  46. package/dist/chunk-GA7U3CIN.js.map +0 -1
  47. package/dist/chunk-KEF3V5ZN.js.map +0 -1
@@ -1,54 +1,6 @@
1
- import { F as FontChoice, a as SiteDoc, A as AnySectionDefinition, R as ReleasePage, o as ReleaseSnapshot, m as Release, j as PageMeta, P as PageDoc, d as PageKind, n as ReleaseMeta, c as SiteDocInput } from './registry-CdgVymsu.js';
1
+ import { h as FontChoice, a as SiteDoc, A as AnySectionDefinition, N as ReleaseSnapshot, R as ReleasePage, J as Release, r as PageMeta, P as PageDoc, f as PageKind, K as ReleaseMeta, b as PostSummary, L as LivePost, x as PostMeta, w as PostDraft, V as SavePostInput, W as SavePostResult, k as ImportPost, l as ImportPostsResult, e as SiteDocInput, u as PostDoc, z as PostStatus } from './registry-D_DCWRTc.js';
2
2
  import { CanonicalBrand } from '@marktiderman/genesis-design-system';
3
3
 
4
- /**
5
- * URL and path rules shared by the schemas, the renderer and the database.
6
- *
7
- * Every link a site renders comes from stored props, so every link is
8
- * untrusted. A link is safe when it is a site path (`/about`), a fragment
9
- * (`#pricing`), or an absolute `https:`, `http:`, `mailto:` or `tel:` URL.
10
- * Everything else (`javascript:`, `data:`, `vbscript:`, protocol-relative
11
- * `//host`, a backslash trick, a control character) is refused, both when a
12
- * document is parsed and again when it is rendered.
13
- */
14
- /** The longest URL a site stores. */
15
- declare const MAX_URL_LENGTH = 2048;
16
- /** Is this a link a site may render: a site path, a fragment, or an http(s), mailto or tel URL? */
17
- declare function isSafeHref(value: unknown): value is string;
18
- /** Is this a URL an image may load from: a site path or an `https:` URL? */
19
- declare function isSafeImageSrc(value: unknown): value is string;
20
- /** Is this an external (absolute) link, opened with `rel="noopener noreferrer"`? */
21
- declare function isExternalHref(href: string): boolean;
22
- /**
23
- * A page's path: `/`, or lowercase segments of letters, digits and hyphens
24
- * (`/about`, `/classes/strength`). No trailing slash, no dots, at most 8
25
- * segments and 200 characters. The database checks the same pattern.
26
- */
27
- declare const PAGE_PATH: RegExp;
28
- declare const MAX_PAGE_PATH_LENGTH = 200;
29
- /**
30
- * Paths a site never owns, because it shares its hostname with the host app:
31
- * the app's API and server functions, its auth callbacks, and this package's
32
- * own assets. A host passes its own list (or a predicate) to
33
- * `handleSiteRequest` and `validatePagePath`; the database's default is the
34
- * same list (`genesis_sites.reserved_paths()`).
35
- */
36
- declare const DEFAULT_RESERVED_PATHS: readonly string[];
37
- /** A reserved-path rule: a list of path prefixes, or a predicate. */
38
- type ReservedPaths = readonly string[] | ((path: string) => boolean);
39
- /** Is `path` reserved: equal to a reserved prefix, or under it (`/api`, `/api/x`, not `/apiary`)? */
40
- declare function isReservedPath(path: string, reserved?: ReservedPaths): boolean;
41
- /** Problems with a page path, empty when it is fine. */
42
- declare function pagePathProblems(path: unknown, reserved?: ReservedPaths): string[];
43
- /** Throws when the path can't be a page's. */
44
- declare function validatePagePath(path: string, reserved?: ReservedPaths): string;
45
- /**
46
- * The page path a request path asks for: decoded, lowercase, without a
47
- * trailing slash. Returns null for a path that can't name a page (so the
48
- * caller falls through to the host app).
49
- */
50
- declare function normalizeRequestPath(pathname: string): string | null;
51
-
52
4
  /**
53
5
  * A site's theme, from its brand: one `<style>` of CSS variables per site.
54
6
  *
@@ -131,8 +83,14 @@ interface ThemePair {
131
83
  mode: "light" | "dark";
132
84
  foreground: string;
133
85
  background: string;
134
- /** The ratio it must reach: 4.5 for text, 3 for the focus ring. */
86
+ /** The ratio it must reach: 4.5 for text, 3 for the focus ring and other non-text marks. */
135
87
  minimum: number;
88
+ /**
89
+ * `error` (default) blocks the publish; `warning` is shown but doesn't. A
90
+ * mark that never carries meaning alone (each one also has text or a shape)
91
+ * warns, so a light brand colour can still publish.
92
+ */
93
+ severity?: "error" | "warning";
136
94
  }
137
95
  interface SiteTheme {
138
96
  light: Record<string, string>;
@@ -157,6 +115,54 @@ declare function siteTheme(brand: BrandInput, theme?: Partial<SiteDoc["theme"]>)
157
115
  /** The site's `<style>` body: the light variables on `:root`, the dark ones by scheme. */
158
116
  declare function themeCss(theme: SiteTheme): string;
159
117
 
118
+ /**
119
+ * URL and path rules shared by the schemas, the renderer and the database.
120
+ *
121
+ * Every link a site renders comes from stored props, so every link is
122
+ * untrusted. A link is safe when it is a site path (`/about`), a fragment
123
+ * (`#pricing`), or an absolute `https:`, `http:`, `mailto:` or `tel:` URL.
124
+ * Everything else (`javascript:`, `data:`, `vbscript:`, protocol-relative
125
+ * `//host`, a backslash trick, a control character) is refused, both when a
126
+ * document is parsed and again when it is rendered.
127
+ */
128
+ /** The longest URL a site stores. */
129
+ declare const MAX_URL_LENGTH = 2048;
130
+ /** Is this a link a site may render: a site path, a fragment, or an http(s), mailto or tel URL? */
131
+ declare function isSafeHref(value: unknown): value is string;
132
+ /** Is this a URL an image may load from: a site path or an `https:` URL? */
133
+ declare function isSafeImageSrc(value: unknown): value is string;
134
+ /** Is this an external (absolute) link, opened with `rel="noopener noreferrer"`? */
135
+ declare function isExternalHref(href: string): boolean;
136
+ /**
137
+ * A page's path: `/`, or lowercase segments of letters, digits and hyphens
138
+ * (`/about`, `/classes/strength`). No trailing slash, no dots, at most 8
139
+ * segments and 200 characters. The database checks the same pattern.
140
+ */
141
+ declare const PAGE_PATH: RegExp;
142
+ declare const MAX_PAGE_PATH_LENGTH = 200;
143
+ /**
144
+ * Paths a site never owns, because it shares its hostname with the host app:
145
+ * the app's API and server functions, its auth callbacks, and this package's
146
+ * own assets. A host passes its own list (or a predicate) to
147
+ * `handleSiteRequest` and `validatePagePath`; the database's default is the
148
+ * same list (`genesis_sites.reserved_paths()`).
149
+ */
150
+ declare const DEFAULT_RESERVED_PATHS: readonly string[];
151
+ /** A reserved-path rule: a list of path prefixes, or a predicate. */
152
+ type ReservedPaths = readonly string[] | ((path: string) => boolean);
153
+ /** Is `path` reserved: equal to a reserved prefix, or under it (`/api`, `/api/x`, not `/apiary`)? */
154
+ declare function isReservedPath(path: string, reserved?: ReservedPaths): boolean;
155
+ /** Problems with a page path, empty when it is fine. */
156
+ declare function pagePathProblems(path: unknown, reserved?: ReservedPaths): string[];
157
+ /** Throws when the path can't be a page's. */
158
+ declare function validatePagePath(path: string, reserved?: ReservedPaths): string;
159
+ /**
160
+ * The page path a request path asks for: decoded, lowercase, without a
161
+ * trailing slash. Returns null for a path that can't name a page (so the
162
+ * caller falls through to the host app).
163
+ */
164
+ declare function normalizeRequestPath(pathname: string): string | null;
165
+
160
166
  interface SiteIssue {
161
167
  severity: "error" | "warning";
162
168
  code: string;
@@ -171,7 +177,7 @@ type Lookup = (type: string) => AnySectionDefinition | undefined;
171
177
  declare function checkPage(page: ReleasePage, lookup?: Lookup, reserved?: ReservedPaths): SiteIssue[];
172
178
  /** Issues with the site's own parts: header, footer, redirects. */
173
179
  declare function checkSiteDoc(site: SiteDoc, lookup?: Lookup): SiteIssue[];
174
- /** Colour pairs below their minimum (1.4.3 text, 1.4.11 focus ring). */
180
+ /** Colour pairs below their minimum (1.4.3 text, 1.4.11 focus ring and primary borders and markers). */
175
181
  declare function checkContrast(theme: SiteTheme): SiteIssue[];
176
182
  interface CheckReleaseOptions {
177
183
  /** The brand's theme: adds the contrast check. */
@@ -185,6 +191,16 @@ interface CheckReleaseOptions {
185
191
  * @stability Beta
186
192
  */
187
193
  declare function checkRelease(snapshot: ReleaseSnapshot, options?: CheckReleaseOptions): SiteIssue[];
194
+ /** The blog's rules: one post page, not the home page, with a post-body section; a post list needs a post page. */
195
+ declare function checkBlog(snapshot: ReleaseSnapshot, lookup?: Lookup): SiteIssue[];
196
+ /**
197
+ * The checks a post passes before it is published: a slug, a title, alt
198
+ * text on its images, and a heading outline under the post's own title
199
+ * (the page's h1): no level 1 heading, none skipped, starting at 2.
200
+ *
201
+ * @stability Beta
202
+ */
203
+ declare function checkPost(slug: string, doc: unknown): SiteIssue[];
188
204
  /**
189
205
  * A rendered page's outline, from its HTML: one `h1`, no skipped heading
190
206
  * level, every `<img>` with an `alt` attribute. For tests and for hosts that
@@ -208,6 +224,17 @@ declare const isConflict: <T>(result: SaveResult<T>) => result is {
208
224
  conflict: T;
209
225
  rev: number;
210
226
  };
227
+ /**
228
+ * The file types a site asset may be, as the database checks them: images
229
+ * and PDF. Never SVG or HTML, which a browser runs as a page.
230
+ */
231
+ declare const ASSET_TYPES: readonly string[];
232
+ /**
233
+ * The storage key for an upload: `sites/<siteId>/<id>-<name>` for an image,
234
+ * `sites/<siteId>/files/<id>-<name>` for a file to download (a PDF), so
235
+ * storage policies scope by site with `can_edit`.
236
+ */
237
+ declare function assetPath(siteId: string, id: string, name: string | undefined, mime: string): string;
211
238
  interface AssetMeta {
212
239
  alt?: string;
213
240
  width?: number;
@@ -275,7 +302,31 @@ interface SiteStore {
275
302
  publish(siteId: string, options?: PublishOptions): Promise<Release>;
276
303
  rollback(siteId: string, releaseId: string): Promise<Release>;
277
304
  listReleases(siteId: string): Promise<ReleaseMeta[]>;
305
+ /** Store an upload under `sites/<siteId>/...` (a PDF under `files/`) and record it. Images and PDF only. */
278
306
  putAsset?(siteId: string, file: Blob, meta?: AssetMeta): Promise<Asset>;
307
+ /** The published posts, newest first, without bodies (at most `MAX_LIVE_POSTS`). Public, like `getLive`. */
308
+ listLivePosts?(siteId: string): Promise<PostSummary[]>;
309
+ /** One published post. Public, like `getLive`. Null for a draft, an unknown slug, or a site that isn't live. */
310
+ getLivePost?(siteId: string, slug: string): Promise<LivePost | null>;
311
+ /** Every post's meta for the editor (drafts too), newest first. */
312
+ listPosts?(siteId: string): Promise<PostMeta[]>;
313
+ getPost?(postId: string): Promise<PostDraft | null>;
314
+ /** Create a post (no `id`) or save its draft by compare-and-swap. A post keeps its slug once published. */
315
+ savePost?(siteId: string, input: SavePostInput): Promise<SavePostResult>;
316
+ /** Check the draft (`checkPost`) and make it the published copy. Throws `PublishBlockedError`. */
317
+ publishPost?(postId: string): Promise<PostMeta>;
318
+ /** Take the post down; its draft stays. */
319
+ unpublishPost?(postId: string): Promise<PostMeta>;
320
+ /** Delete a post (its row stays, marked deleted). Returns its site, for the cache purge; null when there was no such post. */
321
+ deletePost?(postId: string): Promise<{
322
+ siteId: string;
323
+ } | null>;
324
+ /**
325
+ * A one-time import from another site: each post keeps its slug and its
326
+ * date, and is published unless asked otherwise or a check fails (then it
327
+ * is a draft, with its issues). A slug the site already has is skipped.
328
+ */
329
+ importPosts?(siteId: string, posts: readonly ImportPost[]): Promise<ImportPostsResult>;
279
330
  }
280
331
  /** A publish the checks refused: `issues` lists why. */
281
332
  declare class PublishBlockedError extends Error {
@@ -317,7 +368,8 @@ declare function formCacheTag(slug: string): string;
317
368
  /** Why a site's cache is being purged. */
318
369
  interface InvalidationInfo {
319
370
  siteId: string;
320
- action: "publish" | "rollback";
371
+ /** `post`: a post was published, taken down, deleted or imported. */
372
+ action: "publish" | "rollback" | "post";
321
373
  }
322
374
  /**
323
375
  * A store that purges the site's cached pages after every publish and
@@ -339,6 +391,20 @@ interface InvalidationInfo {
339
391
  * @stability Beta
340
392
  */
341
393
  declare function withCacheInvalidation(store: SiteStore, invalidate: (tags: string[], info: InvalidationInfo) => Promise<void> | void): SiteStore;
394
+ /**
395
+ * One post of an import, checked: its document parsed, its status (a draft
396
+ * when asked for, or when a check fails, with the reasons) and its date.
397
+ * Every store's `importPosts` runs it.
398
+ */
399
+ declare function prepareImportPost(item: ImportPost): {
400
+ doc: PostDoc;
401
+ status: PostStatus;
402
+ publishedAt: string;
403
+ issues: string[];
404
+ } | {
405
+ slug: string;
406
+ reason: string;
407
+ };
342
408
  interface NewSite {
343
409
  id?: string;
344
410
  /** The site document (defaults applied). */
@@ -374,4 +440,4 @@ interface MemoryStoreOptions {
374
440
  */
375
441
  declare function createMemorySiteStore(options?: MemoryStoreOptions): MemorySiteStore;
376
442
 
377
- export { siteTheme as $, type Asset as A, type BrandInput as B, type CheckReleaseOptions as C, DEFAULT_RESERVED_PATHS as D, hexToHslTriplet as E, FONT_CHOICES as F, isConflict as G, isExternalHref as H, type InvalidationInfo as I, isPublishable as J, isReservedPath as K, isSafeHref as L, MAX_PAGE_PATH_LENGTH as M, type NewPage as N, isSafeImageSrc as O, PAGE_PATH as P, isSiteBrand as Q, type ReservedPaths as R, type SiteBrand as S, type ThemePair as T, luminance as U, mix as V, normalizeRequestPath as W, pagePathProblems as X, parseHex as Y, prepareRelease as Z, siteCacheTag as _, type AssetMeta as a, stableHash as a0, themeCss as a1, toCanonicalBrand as a2, validatePagePath as a3, withCacheInvalidation as a4, type BrandSource as b, type FontInfo as c, MAX_URL_LENGTH as d, type MemorySiteStore as e, type MemoryStoreOptions as f, type NewSite as g, type PageDraft as h, type PreparedRelease as i, PublishBlockedError as j, type PublishOptions as k, type SaveResult as l, SiteConflictError as m, type SiteDraft as n, type SiteIssue as o, type SiteStore as p, type SiteTheme as q, brandDisplayName as r, checkContrast as s, checkHtmlOutline as t, checkPage as u, checkRelease as v, checkSiteDoc as w, contrastRatio as x, createMemorySiteStore as y, formCacheTag as z };
443
+ export { pagePathProblems as $, ASSET_TYPES as A, type BrandInput as B, type CheckReleaseOptions as C, DEFAULT_RESERVED_PATHS as D, checkSiteDoc as E, FONT_CHOICES as F, contrastRatio as G, createMemorySiteStore as H, type InvalidationInfo as I, formCacheTag as J, hexToHslTriplet as K, isConflict as L, MAX_PAGE_PATH_LENGTH as M, type NewPage as N, isExternalHref as O, PAGE_PATH as P, isPublishable as Q, type ReservedPaths as R, type SiteStore as S, type ThemePair as T, isReservedPath as U, isSafeHref as V, isSafeImageSrc as W, isSiteBrand as X, luminance as Y, mix as Z, normalizeRequestPath as _, type SiteBrand as a, parseHex as a0, prepareImportPost as a1, prepareRelease as a2, siteCacheTag as a3, siteTheme as a4, stableHash as a5, themeCss as a6, toCanonicalBrand as a7, validatePagePath as a8, withCacheInvalidation as a9, type Asset as b, type AssetMeta as c, type BrandSource as d, type FontInfo as e, MAX_URL_LENGTH as f, type MemorySiteStore as g, type MemoryStoreOptions as h, type NewSite as i, type PageDraft as j, type PreparedRelease as k, PublishBlockedError as l, type PublishOptions as m, type SaveResult as n, SiteConflictError as o, type SiteDraft as p, type SiteIssue as q, type SiteTheme as r, assetPath as s, brandDisplayName as t, checkBlog as u, checkContrast as v, checkHtmlOutline as w, checkPage as x, checkPost as y, checkRelease as z };
@@ -1,6 +1,7 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { p as SiteStore } from './store-BzGw2i5J.js';
3
- import './registry-CdgVymsu.js';
2
+ import { S as SiteStore } from './store-DkSycDhq.js';
3
+ export { A as ASSET_TYPES } from './store-DkSycDhq.js';
4
+ import './registry-D_DCWRTc.js';
4
5
  import 'react';
5
6
  import 'zod';
6
7
  import '@marktiderman/genesis-design-system';
@@ -19,8 +20,7 @@ import '@marktiderman/genesis-design-system';
19
20
  */
20
21
 
21
22
  declare const SITES_SCHEMA = "genesis_sites";
22
- /** The file types a site asset may be, as the database checks them. */
23
- declare const ASSET_TYPES: readonly string[];
23
+
24
24
  interface SupabaseSiteStoreOptions {
25
25
  /** The storage bucket for `putAsset`. Default `site-assets`. */
26
26
  bucket?: string;
@@ -38,4 +38,4 @@ declare class SiteStoreError extends Error {
38
38
  */
39
39
  declare function createSupabaseSiteStore(client: SupabaseClient, options?: SupabaseSiteStoreOptions): SiteStore;
40
40
 
41
- export { ASSET_TYPES, SITES_SCHEMA, SiteStoreError, type SupabaseSiteStoreOptions, createSupabaseSiteStore };
41
+ export { SITES_SCHEMA, SiteStoreError, type SupabaseSiteStoreOptions, createSupabaseSiteStore };
package/dist/supabase.js CHANGED
@@ -1,9 +1,10 @@
1
- import { prepareRelease } from './chunk-GA7U3CIN.js';
2
- import { PageDoc, SiteDoc, parseSnapshot, ReleaseSnapshot } from './chunk-KEF3V5ZN.js';
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';
3
5
 
4
6
  // src/supabase/index.ts
5
7
  var SITES_SCHEMA = "genesis_sites";
6
- var ASSET_TYPES = Object.freeze(["image/png", "image/jpeg", "image/webp", "image/avif", "image/gif", "application/pdf"]);
7
8
  var SiteStoreError = class extends Error {
8
9
  constructor(message, code, detail) {
9
10
  super(message);
@@ -29,6 +30,18 @@ var fromRow = (r) => ({
29
30
  ...r.snapshot !== void 0 ? { snapshot: r.snapshot } : {}
30
31
  });
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
+ });
32
45
  function createSupabaseSiteStore(client, options = {}) {
33
46
  const db = client.schema(SITES_SCHEMA);
34
47
  const bucket = options.bucket ?? "site-assets";
@@ -151,9 +164,8 @@ function createSupabaseSiteStore(client, options = {}) {
151
164
  return (data ?? []).map((r) => metaOf(fromRow(r)));
152
165
  },
153
166
  async putAsset(siteId, file, meta = {}) {
154
- const name = (meta.name ?? "file").toLowerCase().replace(/[^a-z0-9.]+/g, "-").replace(/\.{2,}/g, ".").slice(0, 80) || "file";
155
- const path = `sites/${siteId}/${crypto.randomUUID()}-${name}`;
156
167
  const mime = file.type || "application/octet-stream";
168
+ const path = assetPath(siteId, crypto.randomUUID(), meta.name, mime);
157
169
  if (!ASSET_TYPES.includes(mime)) throw new SiteStoreError(`uploading: ${mime} isn't an image (PNG, JPEG, WebP, AVIF, GIF) or a PDF.`, "22023");
158
170
  const upload = await client.storage.from(bucket).upload(path, file, { contentType: mime, upsert: false });
159
171
  if (upload.error) throw new SiteStoreError(`uploading: ${upload.error.message}`, void 0);
@@ -164,11 +176,108 @@ function createSupabaseSiteStore(client, options = {}) {
164
176
  }
165
177
  const url = client.storage.from(bucket).getPublicUrl(path).data.publicUrl;
166
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;
167
276
  }
168
277
  };
169
278
  return store;
170
279
  }
171
280
 
172
- export { ASSET_TYPES, SITES_SCHEMA, SiteStoreError, createSupabaseSiteStore };
281
+ export { SITES_SCHEMA, SiteStoreError, createSupabaseSiteStore };
173
282
  //# sourceMappingURL=supabase.js.map
174
283
  //# sourceMappingURL=supabase.js.map
@@ -1 +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 { PageDoc, ReleaseSnapshot, SiteDoc, parseSnapshot, type PageKind, type PageMeta, type Release, type ReleaseMeta } from \"../core/schemas\";\nimport { 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 const ASSET_TYPES: readonly string[] = Object.freeze([\"image/png\", \"image/jpeg\", \"image/webp\", \"image/avif\", \"image/gif\", \"application/pdf\"]);\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\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 name = (meta.name ?? \"file\").toLowerCase().replace(/[^a-z0-9.]+/g, \"-\").replace(/\\.{2,}/g, \".\").slice(0, 80) || \"file\";\n const path = `sites/${siteId}/${crypto.randomUUID()}-${name}`;\n const mime = file.type || \"application/octet-stream\";\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 return store;\n}\n"]}
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 CHANGED
@@ -1,5 +1,5 @@
1
- import { f as FormsResolver, o as ReleaseSnapshot, R as ReleasePage, q as ResolvedForm } from './registry-CdgVymsu.js';
2
- import { p as SiteStore, b as BrandSource, R as ReservedPaths, c as FontInfo, q as SiteTheme } from './store-BzGw2i5J.js';
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
3
  import * as React from 'react';
4
4
  import 'zod';
5
5
  import '@marktiderman/genesis-design-system';
@@ -7,8 +7,12 @@ import '@marktiderman/genesis-design-system';
7
7
  /** Where this package serves its own files, under the site's base path. Reserved for pages. */
8
8
  declare const ASSETS_PREFIX = "/_gs";
9
9
  interface SiteRequestOptions {
10
- /** The live path needs only `getLive`; preview needs the draft reads too. */
11
- store: Pick<SiteStore, "getLive"> & Partial<Pick<SiteStore, "getDraft" | "listPages" | "getPageDraft">>;
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">>;
12
16
  brand: BrandSource;
13
17
  /** The site this request is for, resolved by the host from the Host header (or a path). */
14
18
  siteId: string;
@@ -16,8 +20,16 @@ interface SiteRequestOptions {
16
20
  * `live` (default) serves the published release, cached at the CDN.
17
21
  * `preview` serves the drafts, `noindex` and `no-store`; the store must be
18
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.
19
24
  */
20
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;
21
33
  /**
22
34
  * The site's primary origin (`https://riverside.example`) for canonical,
23
35
  * Open Graph and sitemap URLs. Default: the request URL's origin. Pass it
@@ -97,6 +109,12 @@ interface SiteRendererProps {
97
109
  assets: SiteAssets;
98
110
  /** The stylesheet URL for a font, or null to rely on installed fonts. Default: Google Fonts. */
99
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;
100
118
  }
101
119
  declare const googleFontHref: (font: FontInfo) => string | null;
102
120
  /** The form slugs a page uses, to resolve before rendering: well-formed slugs of visible form embeds in the page body. */
@@ -106,12 +124,12 @@ declare function collectFormSlugs(_snapshot: ReleaseSnapshot, page: ReleasePage
106
124
  *
107
125
  * @stability Beta
108
126
  */
109
- declare function SiteRenderer({ snapshot, page, theme, origin, basePath, mode, logo, forms, assets, fontHref }: SiteRendererProps): React.JSX.Element;
127
+ declare function SiteRenderer({ snapshot, page, theme, origin, basePath, mode, logo, forms, assets, fontHref, posts }: SiteRendererProps): React.JSX.Element;
110
128
 
111
129
  declare const ASSET_SIZES: {
112
- readonly css: 32183;
113
- readonly formsCss: 72702;
114
- readonly islands: 2431;
130
+ readonly css: 37194;
131
+ readonly formsCss: 74939;
132
+ readonly islands: 3010;
115
133
  readonly formIsland: 502065;
116
134
  };
117
135