@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.
- package/README.md +152 -8
- package/dist/assets/islands.77d11f5697.js +1 -0
- package/dist/assets/sites-forms.4e6aa42497.css +10 -0
- package/dist/assets/sites-forms.css +1 -1
- package/dist/assets/sites.css +1 -1
- package/dist/assets/sites.e1ffd2708b.css +2 -0
- package/dist/blocks.d.ts +1 -1
- package/dist/blocks.js +5 -1
- package/dist/blocks.js.map +1 -1
- package/dist/chunk-3VKTBE7S.js +69 -0
- package/dist/chunk-3VKTBE7S.js.map +1 -0
- package/dist/chunk-C375ZPU2.js +2241 -0
- package/dist/chunk-C375ZPU2.js.map +1 -0
- package/dist/{chunk-GA7U3CIN.js → chunk-IXMY4B56.js} +229 -10
- package/dist/chunk-IXMY4B56.js.map +1 -0
- package/dist/chunk-VZUDRPFG.js +13 -0
- package/dist/chunk-VZUDRPFG.js.map +1 -0
- package/dist/{chunk-KEF3V5ZN.js → chunk-ZGWGQGT2.js} +169 -73
- package/dist/chunk-ZGWGQGT2.js.map +1 -0
- package/dist/import.d.ts +130 -0
- package/dist/import.js +330 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +40 -7
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/{registry-CdgVymsu.d.ts → registry-D_DCWRTc.d.ts} +274 -8
- package/dist/sections.d.ts +422 -6
- package/dist/sections.js +4 -2
- package/dist/{store-BzGw2i5J.d.ts → store-DkSycDhq.d.ts} +119 -53
- package/dist/supabase.d.ts +5 -5
- package/dist/supabase.js +115 -6
- package/dist/supabase.js.map +1 -1
- package/dist/web.d.ts +26 -8
- package/dist/web.js +142 -18
- package/dist/web.js.map +1 -1
- package/migrations/0001_genesis_sites.sql +64 -6
- package/migrations/0002_genesis_sites_posts.sql +404 -0
- package/migrations/tests/0001_genesis_sites.test.sql +30 -1
- package/migrations/tests/0002_genesis_sites_posts.test.sql +242 -0
- package/package.json +18 -10
- package/dist/assets/islands.230b20b9c4.js +0 -1
- package/dist/assets/sites-forms.5e6ebf31c3.css +0 -10
- package/dist/assets/sites.b28b796c6f.css +0 -2
- package/dist/chunk-43XUAO63.js +0 -1255
- package/dist/chunk-43XUAO63.js.map +0 -1
- package/dist/chunk-GA7U3CIN.js.map +0 -1
- package/dist/chunk-KEF3V5ZN.js.map +0 -1
|
@@ -1,54 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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 };
|
package/dist/supabase.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
-
|
|
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 {
|
|
41
|
+
export { SITES_SCHEMA, SiteStoreError, type SupabaseSiteStoreOptions, createSupabaseSiteStore };
|
package/dist/supabase.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { prepareRelease } from './chunk-
|
|
2
|
-
|
|
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 {
|
|
281
|
+
export { SITES_SCHEMA, SiteStoreError, createSupabaseSiteStore };
|
|
173
282
|
//# sourceMappingURL=supabase.js.map
|
|
174
283
|
//# sourceMappingURL=supabase.js.map
|
package/dist/supabase.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/supabase/index.ts"],"names":[],"mappings":";;;;AAgBO,IAAM,YAAA,GAAe;AAGrB,IAAM,WAAA,GAAiC,MAAA,CAAO,MAAA,CAAO,CAAC,WAAA,EAAa,cAAc,YAAA,EAAc,YAAA,EAAc,WAAA,EAAa,iBAAiB,CAAC;AAQ5I,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA,EACxC,WAAA,CACE,OAAA,EACS,IAAA,EACA,MAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAHJ,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAGT,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EACd;AAAA,EALW,IAAA;AAAA,EACA,MAAA;AAKb;AAQA,SAAS,IAAA,CAAK,OAAgB,IAAA,EAAqB;AACjD,EAAA,MAAM,IAAI,cAAA,CAAe,CAAA,EAAG,IAAI,CAAA,EAAA,EAAK,KAAA,CAAM,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,OAAO,CAAA;AACjF;AAaA,IAAM,MAAA,GAAS,CAAC,CAAA,MAAiC,EAAE,EAAA,EAAI,EAAE,EAAA,EAAI,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAQ,MAAA,EAAQ,CAAA,CAAE,QAAQ,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,SAAA,EAAW,CAAA,CAAE,SAAA,EAAW,aAAa,CAAA,CAAE,WAAA,EAAa,SAAA,EAAW,CAAA,CAAE,SAAA,EAAU,CAAA;AAa1L,IAAM,OAAA,GAAU,CAAC,CAAA,MAAgC;AAAA,EAC/C,IAAI,CAAA,CAAE,EAAA;AAAA,EACN,QAAQ,CAAA,CAAE,OAAA;AAAA,EACV,QAAQ,CAAA,CAAE,MAAA;AAAA,EACV,MAAM,CAAA,CAAE,IAAA;AAAA,EACR,WAAW,CAAA,CAAE,UAAA;AAAA,EACb,aAAa,CAAA,CAAE,YAAA;AAAA,EACf,WAAW,CAAA,CAAE,UAAA;AAAA,EACb,GAAI,EAAE,QAAA,KAAa,MAAA,GAAY,EAAE,QAAA,EAAU,CAAA,CAAE,QAAA,EAAS,GAAI;AAC5D,CAAA,CAAA;AAaA,IAAM,QAAA,GAAW,CAAC,CAAA,MAA0B,EAAE,EAAA,EAAI,EAAE,EAAA,EAAI,MAAA,EAAQ,CAAA,CAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAM,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,KAAA,EAAO,CAAA,CAAE,KAAA,IAAS,EAAA,EAAI,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,GAAA,EAAK,CAAA,CAAE,SAAA,EAAU,CAAA;AAOrJ,SAAS,uBAAA,CAAwB,MAAA,EAAwB,OAAA,GAAoC,EAAC,EAAc;AACjH,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,MAAA,CAAO,YAAY,CAAA;AACrC,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,aAAA;AAEjC,EAAA,eAAe,QAAQ,EAAA,EAA8B;AACnD,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,GAAG,IAAA,CAAK,eAAe,CAAA,CAAE,MAAA,CAAO,2EAA2E,CAAA,CAAE,EAAA,CAAG,IAAA,EAAM,EAAE,EAAE,MAAA,EAAO;AAC/J,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,qBAAqB,CAAA;AAC5C,IAAA,MAAM,CAAA,GAAI,QAAQ,IAAkB,CAAA;AACpC,IAAA,OAAO,EAAE,GAAG,MAAA,CAAO,CAAC,CAAA,EAAG,UAAU,eAAA,CAAgB,KAAA,CAAM,CAAA,CAAE,QAAQ,CAAA,EAAE;AAAA,EACrE;AAEA,EAAA,eAAe,QAAA,CAAS,QAAgB,SAAA,EAAwC;AAC9E,IAAA,MAAM,OAAA,GAAU,YAAY,iDAAA,GAAoD,4EAAA;AAChF,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,GAAG,IAAA,CAAK,YAAY,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,GAAG,SAAA,EAAW,MAAM,CAAA,CAAE,EAAA,CAAG,YAAA,EAAc,IAAI,EAAE,KAAA,CAAM,MAAM,CAAA,CAAE,KAAA,CAAM,MAAM,CAAA;AAC3I,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,eAAe,CAAA;AACtC,IAAA,OAAQ,QAAQ,EAAC;AAAA,EACnB;AAEA,EAAA,MAAM,KAAA,GAAmB;AAAA,IACvB,MAAM,QAAQ,MAAA,EAAQ;AACpB,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,GAAA,CAAI,cAAA,EAAgB,EAAE,OAAA,EAAS,MAAA,EAAQ,CAAA;AACxE,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,uBAAuB,CAAA;AAC9C,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,MAAA,MAAM,CAAA,GAAI,IAAA;AAEV,MAAA,MAAM,QAAA,GAAW,aAAA,CAAc,CAAA,CAAE,QAAA,EAAU,CAAC,CAAA,KAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,yCAAA,EAA4C,MAAM,CAAA,EAAA,EAAK,CAAC,EAAE,CAAC,CAAA;AAC3H,MAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,MAAA,OAAO,EAAE,GAAG,MAAA,CAAO,CAAC,GAAG,QAAA,EAAS;AAAA,IAClC,CAAA;AAAA,IACA,MAAM,SAAS,MAAA,EAAQ;AACrB,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,GAAG,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,CAAO,kBAAkB,CAAA,CAAE,EAAA,CAAG,IAAA,EAAM,MAAM,EAAE,WAAA,EAAY;AACvG,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,kBAAkB,CAAA;AACzC,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,MAAA,OAAO,EAAE,MAAM,OAAA,CAAQ,KAAA,CAAO,KAA4B,KAAK,CAAA,EAAG,GAAA,EAAM,IAAA,CAA+B,SAAA,EAAU;AAAA,IACnH,CAAA;AAAA,IACA,MAAM,aAAA,CAAc,MAAA,EAAQ,GAAA,EAAK,IAAA,EAAoC;AACnE,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA;AAC9B,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,GAAA,CAAI,iBAAA,EAAmB,EAAE,OAAA,EAAS,MAAA,EAAQ,YAAA,EAAc,GAAA,EAAK,KAAK,CAAA;AACnG,MAAA,IAAI,KAAA,EAAO,SAAS,OAAA,EAAS;AAC3B,QAAA,MAAM,OAAA,GAAU,MAAM,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAC3C,QAAA,IAAI,CAAC,OAAA,EAAS,IAAA,CAAK,KAAA,EAAO,iBAAiB,CAAA;AAC3C,QAAA,OAAO,EAAE,QAAA,EAAU,OAAA,CAAQ,IAAA,EAAM,GAAA,EAAK,QAAQ,GAAA,EAAI;AAAA,MACpD;AACA,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,iBAAiB,CAAA;AACxC,MAAA,OAAO,EAAE,KAAK,IAAA,EAAe;AAAA,IAC/B,CAAA;AAAA,IACA,MAAM,UAAU,MAAA,EAAQ;AACtB,MAAA,OAAA,CAAQ,MAAM,QAAA,CAAS,MAAA,EAAQ,KAAK,CAAA,EAAG,IAAI,QAAQ,CAAA;AAAA,IACrD,CAAA;AAAA,IACA,MAAM,aAAa,MAAA,EAAQ;AACzB,MAAA,MAAM,EAAE,MAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,IAAA,CAAK,YAAY,CAAA,CAAE,MAAA,CAAO,2CAA2C,CAAA,CAAE,EAAA,CAAG,MAAM,MAAM,CAAA,CAAE,GAAG,YAAA,EAAc,IAAI,EAAE,WAAA,EAAY;AAC5J,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,kBAAkB,CAAA;AACzC,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,MAAA,MAAM,GAAA,GAAM,IAAA;AACZ,MAAA,OAAO,EAAE,GAAA,EAAK,OAAA,CAAQ,MAAM,GAAA,CAAI,KAAK,GAAG,GAAA,EAAK,GAAA,CAAI,SAAA,EAAW,MAAA,EAAQ,IAAI,OAAA,EAAS,IAAA,EAAM,IAAI,IAAA,EAAM,IAAA,EAAM,IAAI,IAAA,EAAK;AAAA,IAClH,CAAA;AAAA,IACA,MAAM,aAAA,CAAc,MAAA,EAAQ,GAAA,EAAK,GAAA,EAAmC;AAClE,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAChC,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,GAAA,CAAI,iBAAA,EAAmB,EAAE,SAAS,MAAA,EAAQ,YAAA,EAAc,GAAA,EAAK,GAAA,EAAK,QAAQ,CAAA;AAC3G,MAAA,IAAI,KAAA,EAAO,SAAS,OAAA,EAAS;AAC3B,QAAA,MAAM,OAAA,GAAU,MAAM,KAAA,CAAM,YAAA,CAAa,MAAM,CAAA;AAC/C,QAAA,IAAI,CAAC,OAAA,EAAS,IAAA,CAAK,KAAA,EAAO,iBAAiB,CAAA;AAC3C,QAAA,OAAO,EAAE,QAAA,EAAU,OAAA,CAAQ,GAAA,EAAK,GAAA,EAAK,QAAQ,GAAA,EAAI;AAAA,MACnD;AACA,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,iBAAiB,CAAA;AACxC,MAAA,OAAO,EAAE,KAAK,IAAA,EAAe;AAAA,IAC/B,CAAA;AAAA,IACA,MAAM,UAAA,CAAW,MAAA,EAAQ,IAAA,EAAM;AAC7B,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA;AAClC,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,KAAU,MAAM,EAAA,CAAG,IAAI,aAAA,EAAe,EAAE,SAAS,MAAA,EAAQ,IAAA,EAAM,KAAK,IAAA,EAAM,IAAA,EAAM,KAAK,IAAA,IAAQ,MAAA,EAAQ,KAAK,CAAA;AACxH,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,mBAAmB,CAAA;AAC1C,MAAA,MAAM,IAAA,GAAA,CAAQ,MAAM,KAAA,CAAM,SAAA,CAAU,MAAM,CAAA,EAAG,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,IAAI,CAAA;AACtE,MAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,cAAA,CAAe,gDAAgD,MAAS,CAAA;AAC7F,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,QAAA,CAAS,MAAA,EAAQ,IAAA,EAAM;AAC3B,MAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,GAAA,CAAI,WAAA,EAAa,EAAE,OAAA,EAAS,MAAA,EAAQ,QAAA,EAAU,IAAA,EAAM,CAAA;AAC/E,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,iBAAiB,CAAA;AACxC,MAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,YAAA,CAAa,MAAM,CAAA;AAC7C,MAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,cAAA,CAAe,0CAA0C,MAAS,CAAA;AACxF,MAAA,MAAM,IAAA,GAAA,CAAQ,MAAM,KAAA,CAAM,SAAA,CAAU,KAAA,CAAM,MAAM,CAAA,EAAG,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,MAAM,CAAA;AAC9E,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,WAAW,MAAA,EAAQ;AACvB,MAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,IAAI,aAAA,EAAe,EAAE,OAAA,EAAS,MAAA,EAAQ,CAAA;AACjE,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,mBAAmB,CAAA;AAAA,IAC5C,CAAA;AAAA,IACA,MAAM,OAAA,CAAQ,MAAA,EAAQ,IAAA,GAAuB,EAAC,EAAG;AAC/C,MAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AACzC,MAAA,IAAI,CAAC,OAAO,MAAM,IAAI,eAAe,CAAA,QAAA,EAAW,MAAM,2BAA2B,OAAO,CAAA;AACxF,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,MAAA,EAAQ,IAAI,CAAA;AACxC,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,EAAA,EAAI,CAAA,CAAE,EAAA,EAAI,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,MAAM,CAAA,CAAE,IAAA,EAAM,GAAA,EAAK,OAAA,CAAQ,KAAA,CAAM,CAAA,CAAE,KAAK,CAAA,EAAG,GAAA,EAAK,CAAA,CAAE,SAAA,EAAU,CAAE,CAAA;AAEvH,MAAA,MAAM,QAAA,GAAW,cAAA,CAAe,KAAA,CAAM,IAAA,EAAM,OAAO,IAAI,CAAA;AAEvD,MAAA,MAAM,IAAA,GAA+B,MAAA,CAAO,WAAA,CAAY,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,GAAG,CAAC,CAAC,CAAA;AACvF,MAAA,IAAI,UAAU,KAAA,CAAM,GAAA;AACpB,MAAA,KAAA,MAAW,CAAA,IAAK,SAAS,aAAA,EAAe;AACtC,QAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,aAAA,CAAc,CAAA,CAAE,EAAA,EAAI,IAAA,CAAK,CAAA,CAAE,EAAE,CAAA,EAAI,CAAA,CAAE,GAAG,CAAA;AAChE,QAAA,IAAI,cAAc,KAAA,EAAO,MAAM,IAAI,cAAA,CAAe,iDAAiD,OAAO,CAAA;AAC1G,QAAA,IAAA,CAAK,CAAA,CAAE,EAAE,CAAA,GAAI,KAAA,CAAM,GAAA;AAAA,MACrB;AACA,MAAA,IAAI,SAAS,YAAA,EAAc;AACzB,QAAA,MAAM,QAAQ,MAAM,KAAA,CAAM,cAAc,MAAA,EAAQ,OAAA,EAAS,SAAS,YAAY,CAAA;AAC9E,QAAA,IAAI,cAAc,KAAA,EAAO,MAAM,IAAI,cAAA,CAAe,mDAAmD,OAAO,CAAA;AAC5G,QAAA,OAAA,GAAU,KAAA,CAAM,GAAA;AAAA,MAClB;AACA,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,KAAU,MAAM,EAAA,CAAG,IAAI,cAAA,EAAgB;AAAA,QACnD,OAAA,EAAS,MAAA;AAAA,QACT,iBAAA,EAAmB,OAAA;AAAA,QACnB,cAAA,EAAgB,IAAA;AAAA,QAChB,IAAA,EAAM,KAAK,IAAA,IAAQ,IAAA;AAAA,QACnB,YAAY,QAAA,CAAS;AAAA,OACtB,CAAA;AACD,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,YAAY,CAAA;AACnC,MAAA,OAAO,OAAA,CAAS,KAAqB,EAAE,CAAA;AAAA,IACzC,CAAA;AAAA,IACA,MAAM,QAAA,CAAS,MAAA,EAAQ,SAAA,EAAW;AAChC,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,GAAA,CAAI,eAAA,EAAiB,EAAE,OAAA,EAAS,MAAA,EAAQ,UAAA,EAAY,WAAW,CAAA;AAChG,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,cAAc,CAAA;AACrC,MAAA,OAAO,OAAA,CAAS,KAAqB,EAAE,CAAA;AAAA,IACzC,CAAA;AAAA,IACA,MAAM,aAAa,MAAA,EAAQ;AACzB,MAAA,MAAM,EAAE,MAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAAG,IAAA,CAAK,eAAe,CAAA,CAAE,MAAA,CAAO,iEAAiE,CAAA,CAAE,EAAA,CAAG,WAAW,MAAM,CAAA,CAAE,MAAM,QAAA,EAAU,EAAE,SAAA,EAAW,KAAA,EAAO,CAAA;AAC3L,MAAA,IAAI,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,kBAAkB,CAAA;AACzC,MAAA,OAAA,CAAS,IAAA,IAAQ,EAAC,EAAoB,GAAA,CAAI,CAAC,MAAM,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAC,CAAC,CAAA;AAAA,IACrE,CAAA;AAAA,IACA,MAAM,QAAA,CAAS,MAAA,EAAQ,IAAA,EAAY,IAAA,GAAkB,EAAC,EAAmB;AACvE,MAAA,MAAM,QAAQ,IAAA,CAAK,IAAA,IAAQ,MAAA,EAAQ,WAAA,GAAc,OAAA,CAAQ,cAAA,EAAgB,GAAG,CAAA,CAAE,QAAQ,SAAA,EAAW,GAAG,EAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,IAAK,MAAA;AACtH,MAAA,MAAM,IAAA,GAAO,SAAS,MAAM,CAAA,CAAA,EAAI,OAAO,UAAA,EAAY,IAAI,IAAI,CAAA,CAAA;AAC3D,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,IAAQ,0BAAA;AAG1B,MAAA,IAAI,CAAC,WAAA,CAAY,QAAA,CAAS,IAAI,CAAA,EAAG,MAAM,IAAI,cAAA,CAAe,CAAA,WAAA,EAAc,IAAI,CAAA,sDAAA,CAAA,EAA0D,OAAO,CAAA;AAC7I,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,OAAA,CAAQ,KAAK,MAAM,CAAA,CAAE,MAAA,CAAO,IAAA,EAAM,MAAM,EAAE,WAAA,EAAa,IAAA,EAAM,MAAA,EAAQ,OAAO,CAAA;AACxG,MAAA,IAAI,MAAA,CAAO,KAAA,EAAO,MAAM,IAAI,cAAA,CAAe,cAAc,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,CAAA,EAAI,MAAS,CAAA;AAC1F,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,EAAA,CAC3B,IAAA,CAAK,aAAa,CAAA,CAClB,MAAA,CAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,MAAM,IAAA,EAAM,GAAA,EAAK,IAAA,CAAK,GAAA,IAAO,IAAA,EAAM,KAAA,EAAO,IAAA,CAAK,KAAA,IAAS,MAAM,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,IAAA,EAAM,CAAA,CACrH,MAAA,CAAO,IAAI,EACX,MAAA,EAAO;AACV,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,MAAM,MAAA,CAAO,QAAQ,IAAA,CAAK,MAAM,EAAE,MAAA,CAAO,CAAC,IAAI,CAAC,CAAA;AAC/C,QAAA,IAAA,CAAK,OAAO,sBAAsB,CAAA;AAAA,MACpC;AACA,MAAA,MAAM,GAAA,GAAM,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,CAAE,YAAA,CAAa,IAAI,CAAA,CAAE,IAAA,CAAK,SAAA;AAChE,MAAA,OAAO,EAAE,EAAA,EAAK,IAAA,CAAwB,IAAI,MAAA,EAAQ,IAAA,EAAM,KAAK,IAAA,EAAM,GAAA,EAAK,KAAK,GAAA,IAAO,IAAA,EAAM,OAAO,IAAA,CAAK,KAAA,IAAS,MAAM,MAAA,EAAQ,IAAA,CAAK,UAAU,IAAA,EAAK;AAAA,IACnJ;AAAA,GACF;AACA,EAAA,OAAO,KAAA;AACT","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 {
|
|
2
|
-
import {
|
|
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
|
-
/**
|
|
11
|
-
|
|
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:
|
|
113
|
-
readonly formsCss:
|
|
114
|
-
readonly islands:
|
|
130
|
+
readonly css: 37194;
|
|
131
|
+
readonly formsCss: 74939;
|
|
132
|
+
readonly islands: 3010;
|
|
115
133
|
readonly formIsland: 502065;
|
|
116
134
|
};
|
|
117
135
|
|