@marktiderman/genesis-sites 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +465 -2
  3. package/dist/assets/form-island.c0f6fac859.js +51 -0
  4. package/dist/assets/islands.77d11f5697.js +1 -0
  5. package/dist/assets/sites-forms.4e6aa42497.css +10 -0
  6. package/dist/assets/sites-forms.css +10 -0
  7. package/dist/assets/sites.css +2 -0
  8. package/dist/assets/sites.e1ffd2708b.css +2 -0
  9. package/dist/blocks.d.ts +13 -0
  10. package/dist/blocks.js +26 -0
  11. package/dist/blocks.js.map +1 -0
  12. package/dist/chunk-3VKTBE7S.js +69 -0
  13. package/dist/chunk-3VKTBE7S.js.map +1 -0
  14. package/dist/chunk-C375ZPU2.js +2241 -0
  15. package/dist/chunk-C375ZPU2.js.map +1 -0
  16. package/dist/chunk-IXMY4B56.js +877 -0
  17. package/dist/chunk-IXMY4B56.js.map +1 -0
  18. package/dist/chunk-VZUDRPFG.js +13 -0
  19. package/dist/chunk-VZUDRPFG.js.map +1 -0
  20. package/dist/chunk-ZGWGQGT2.js +595 -0
  21. package/dist/chunk-ZGWGQGT2.js.map +1 -0
  22. package/dist/import.d.ts +130 -0
  23. package/dist/import.js +330 -0
  24. package/dist/import.js.map +1 -0
  25. package/dist/index.d.ts +273 -0
  26. package/dist/index.js +254 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/registry-D_DCWRTc.d.ts +1030 -0
  29. package/dist/sections.d.ts +802 -0
  30. package/dist/sections.js +6 -0
  31. package/dist/sections.js.map +1 -0
  32. package/dist/store-DkSycDhq.d.ts +443 -0
  33. package/dist/supabase.d.ts +41 -0
  34. package/dist/supabase.js +283 -0
  35. package/dist/supabase.js.map +1 -0
  36. package/dist/web.d.ts +136 -0
  37. package/dist/web.js +479 -0
  38. package/dist/web.js.map +1 -0
  39. package/migrations/0001_genesis_sites.sql +821 -0
  40. package/migrations/0002_genesis_sites_posts.sql +404 -0
  41. package/migrations/tests/0001_genesis_sites.test.sql +349 -0
  42. package/migrations/tests/0002_genesis_sites_posts.test.sql +242 -0
  43. package/package.json +152 -3
@@ -0,0 +1,6 @@
1
+ export { Anchor, ButtonLink, CHIP_TONES, HiddenTitle, ICONS, IconName, ItemTitle, MAP_PROVIDERS, SectionBand, SectionIntro, SectionTitle, SiteFormHeader, SiteImage, TextLink, WEEKDAYS, blogListSection, builtInSections, ctaSection, faqSection, featuresSection, fileDescription, fileHref, fileSection, footerSection, formEmbedSection, formatPostDate, formatTime, gallerySection, heroSection, linkAttrs, logosSection, mapEmbedUrl, mapPageUrl, mapSection, navSection, postBodySection, pricingSection, resolveHref, richTextSection, scheduleSection, siteFormSlots, statsSection, teamSection, testimonialsSection, timelineSection, titleId } from './chunk-C375ZPU2.js';
2
+ import './chunk-VZUDRPFG.js';
3
+ import './chunk-ZGWGQGT2.js';
4
+ import './chunk-3VKTBE7S.js';
5
+ //# sourceMappingURL=sections.js.map
6
+ //# sourceMappingURL=sections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"sections.js"}
@@ -0,0 +1,443 @@
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
+ import { CanonicalBrand } from '@marktiderman/genesis-design-system';
3
+
4
+ /**
5
+ * A site's theme, from its brand: one `<style>` of CSS variables per site.
6
+ *
7
+ * The brand comes from the host (`BrandSource`) at render time, never copied
8
+ * into the site. A publish checks the brand's colours and records its hash;
9
+ * a page rendered with a different brand (edited since) is served with a
10
+ * short cache and a warning until the site is published again, which checks
11
+ * the new colours and purges the old pages. It
12
+ * may be a full `CanonicalBrand` or the minimal shape a brand screen collects
13
+ * (`SiteBrand`: a name, a primary colour for light and dark, a heading and a
14
+ * body font from a fixed list, a logo).
15
+ *
16
+ * The variables are design-system `themeFromBrand(brand).variables` (the
17
+ * brand's scales, `--color-*`) plus the genesis-ui roles the sections are
18
+ * styled with (`--primary`, `--background`, `--font-display`, ...), so the
19
+ * one shared stylesheet serves every brand. Foregrounds are picked for
20
+ * contrast: the text on a primary button is whichever of the brand's light
21
+ * or dark text reads better, and the focus ring falls back to the text
22
+ * colour when the primary is too faint on the page.
23
+ */
24
+
25
+ /** The minimal brand: what a brand screen collects. */
26
+ interface SiteBrand {
27
+ name: string;
28
+ colors: {
29
+ /** The primary colour in light mode, as #rrggbb. */
30
+ primary: string;
31
+ /** The primary in dark mode. Default: the light primary, lightened. */
32
+ primaryDark?: string;
33
+ };
34
+ fonts?: {
35
+ heading?: FontChoice;
36
+ body?: FontChoice;
37
+ };
38
+ /** The logo, shown in the header and footer and named in JSON-LD. */
39
+ logo?: {
40
+ src: string;
41
+ alt?: string;
42
+ };
43
+ }
44
+ /** What a `BrandSource` may return: the minimal brand, or a full design-system brand (with an optional logo). */
45
+ type BrandInput = SiteBrand | (CanonicalBrand & {
46
+ logo?: {
47
+ src: string;
48
+ alt?: string;
49
+ };
50
+ displayName?: string;
51
+ });
52
+ /** The host's brand, read at render time. */
53
+ interface BrandSource {
54
+ getBrand(siteId: string): Promise<BrandInput | null> | BrandInput | null;
55
+ }
56
+ interface FontInfo {
57
+ label: string;
58
+ /** The CSS font stack. */
59
+ stack: string;
60
+ /** The Google Fonts `family=` value, when it isn't a system font. */
61
+ google?: string;
62
+ }
63
+ /** The fonts a brand or site may choose. */
64
+ declare const FONT_CHOICES: Readonly<Record<FontChoice, FontInfo>>;
65
+ /** `#abc` / `#aabbcc` → [r, g, b] (0–255). Throws on anything else. */
66
+ declare function parseHex(hex: string): [number, number, number];
67
+ /** WCAG relative luminance. */
68
+ declare function luminance(hex: string): number;
69
+ /** WCAG contrast ratio (1–21) of two colours. */
70
+ declare function contrastRatio(a: string, b: string): number;
71
+ /** Mix a colour toward white (`amount` > 0) or black (< 0), 0–1. */
72
+ declare function mix(hex: string, amount: number): string;
73
+ /** `#4f46e5` → `243 75% 59%`: the HSL triplet genesis-ui's variables hold. */
74
+ declare function hexToHslTriplet(hex: string): string;
75
+ declare function isSiteBrand(brand: BrandInput): brand is SiteBrand;
76
+ /** The minimal brand as a design-system brand (Genesis's neutrals and status colours, the brand's primary and fonts). */
77
+ declare function toCanonicalBrand(brand: BrandInput): CanonicalBrand;
78
+ /** The brand's display name: the minimal brand's `name`, a canonical brand's `displayName` or `name`. */
79
+ declare function brandDisplayName(brand: BrandInput): string;
80
+ interface ThemePair {
81
+ /** What the pair is, for an accessibility issue: "body text on the page". */
82
+ name: string;
83
+ mode: "light" | "dark";
84
+ foreground: string;
85
+ background: string;
86
+ /** The ratio it must reach: 4.5 for text, 3 for the focus ring and other non-text marks. */
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";
94
+ }
95
+ interface SiteTheme {
96
+ light: Record<string, string>;
97
+ dark: Record<string, string>;
98
+ colorScheme: "light" | "dark" | "auto";
99
+ /** The fonts the site uses, to load. */
100
+ fonts: FontInfo[];
101
+ /** The colour pairs the sections draw, for the contrast check. */
102
+ pairs: ThemePair[];
103
+ /** Hash of the brand and overrides it was made from (recorded on a release). */
104
+ brandHash: string;
105
+ }
106
+ /** FNV-1a, 32 bits twice: a short, stable hash for cache keys and release records (not for security). */
107
+ declare function stableHash(value: unknown): string;
108
+ /**
109
+ * The site's theme: variables for light and dark, the fonts to load, the
110
+ * pairs the contrast check reads, and a hash of what it was made from.
111
+ *
112
+ * @stability Beta
113
+ */
114
+ declare function siteTheme(brand: BrandInput, theme?: Partial<SiteDoc["theme"]>): SiteTheme;
115
+ /** The site's `<style>` body: the light variables on `:root`, the dark ones by scheme. */
116
+ declare function themeCss(theme: SiteTheme): string;
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
+
166
+ interface SiteIssue {
167
+ severity: "error" | "warning";
168
+ code: string;
169
+ message: string;
170
+ pageId?: string;
171
+ path?: string;
172
+ sectionId?: string;
173
+ }
174
+ declare const isPublishable: (issues: readonly SiteIssue[]) => boolean;
175
+ type Lookup = (type: string) => AnySectionDefinition | undefined;
176
+ /** Issues on one page: its sections, limits and the h1. */
177
+ declare function checkPage(page: ReleasePage, lookup?: Lookup, reserved?: ReservedPaths): SiteIssue[];
178
+ /** Issues with the site's own parts: header, footer, redirects. */
179
+ declare function checkSiteDoc(site: SiteDoc, lookup?: Lookup): SiteIssue[];
180
+ /** Colour pairs below their minimum (1.4.3 text, 1.4.11 focus ring and primary borders and markers). */
181
+ declare function checkContrast(theme: SiteTheme): SiteIssue[];
182
+ interface CheckReleaseOptions {
183
+ /** The brand's theme: adds the contrast check. */
184
+ theme?: SiteTheme;
185
+ lookup?: Lookup;
186
+ reserved?: ReservedPaths;
187
+ }
188
+ /**
189
+ * Everything a publish checks, for a whole release.
190
+ *
191
+ * @stability Beta
192
+ */
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[];
204
+ /**
205
+ * A rendered page's outline, from its HTML: one `h1`, no skipped heading
206
+ * level, every `<img>` with an `alt` attribute. For tests and for hosts that
207
+ * want to audit what was actually served.
208
+ */
209
+ declare function checkHtmlOutline(html: string): SiteIssue[];
210
+
211
+ /**
212
+ * Where sites live: the `SiteStore` contract, the publish pipeline every
213
+ * store shares, cache invalidation, and an in-memory store for tests, demos
214
+ * and previews. `@marktiderman/genesis-sites/supabase` has the Postgres one.
215
+ */
216
+
217
+ type SaveResult<T> = {
218
+ rev: number;
219
+ } | {
220
+ conflict: T;
221
+ rev: number;
222
+ };
223
+ declare const isConflict: <T>(result: SaveResult<T>) => result is {
224
+ conflict: T;
225
+ rev: number;
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;
238
+ interface AssetMeta {
239
+ alt?: string;
240
+ width?: number;
241
+ height?: number;
242
+ /** The file's name, for the storage key. */
243
+ name?: string;
244
+ }
245
+ interface Asset {
246
+ id: string;
247
+ siteId: string;
248
+ /** The storage key: always `sites/<siteId>/...`, so storage policies can scope by site. */
249
+ path: string;
250
+ /** Where the browser loads it. */
251
+ url: string;
252
+ mime: string;
253
+ alt: string | null;
254
+ width: number | null;
255
+ height: number | null;
256
+ }
257
+ interface PublishOptions {
258
+ note?: string;
259
+ /** The brand to check contrast against (and record the hash of). Required: a publish without it is blocked. */
260
+ brand?: BrandInput | null;
261
+ reserved?: ReservedPaths;
262
+ }
263
+ interface NewPage {
264
+ path: string;
265
+ kind?: PageKind;
266
+ doc: PageDoc;
267
+ }
268
+ interface SiteDraft {
269
+ site: SiteDoc;
270
+ rev: number;
271
+ }
272
+ interface PageDraft {
273
+ doc: PageDoc;
274
+ rev: number;
275
+ siteId: string;
276
+ path: string;
277
+ kind: PageKind;
278
+ }
279
+ /**
280
+ * A site's storage.
281
+ *
282
+ * Drafts change by compare-and-swap on a revision: a save names the revision
283
+ * it started from and gets the new one, or the current document when someone
284
+ * saved first. A publish checks the drafts (`checkRelease`) and freezes them
285
+ * into an immutable release that the live site points at; a rollback moves
286
+ * the pointer back. `getLive` is the public read and needs no session.
287
+ *
288
+ * @stability Beta
289
+ */
290
+ interface SiteStore {
291
+ /** The release the live site serves, or null (not published, archived, or unknown). */
292
+ getLive(siteId: string): Promise<Release | null>;
293
+ getDraft(siteId: string): Promise<SiteDraft | null>;
294
+ saveSiteDraft(siteId: string, rev: number, site: SiteDoc): Promise<SaveResult<SiteDoc>>;
295
+ listPages(siteId: string): Promise<PageMeta[]>;
296
+ getPageDraft(pageId: string): Promise<PageDraft | null>;
297
+ savePageDraft(pageId: string, rev: number, doc: PageDoc): Promise<SaveResult<PageDoc>>;
298
+ createPage(siteId: string, page: NewPage): Promise<PageMeta>;
299
+ /** Move a page to a new path; the old path redirects to it (301). */
300
+ movePage(pageId: string, path: string): Promise<PageMeta>;
301
+ deletePage(pageId: string): Promise<void>;
302
+ publish(siteId: string, options?: PublishOptions): Promise<Release>;
303
+ rollback(siteId: string, releaseId: string): Promise<Release>;
304
+ listReleases(siteId: string): Promise<ReleaseMeta[]>;
305
+ /** Store an upload under `sites/<siteId>/...` (a PDF under `files/`) and record it. Images and PDF only. */
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>;
330
+ }
331
+ /** A publish the checks refused: `issues` lists why. */
332
+ declare class PublishBlockedError extends Error {
333
+ readonly issues: SiteIssue[];
334
+ constructor(issues: SiteIssue[]);
335
+ }
336
+ /** Someone saved first; the store's current document is in the result of the save that raised this. */
337
+ declare class SiteConflictError extends Error {
338
+ constructor(message?: string);
339
+ }
340
+ interface PreparedRelease {
341
+ snapshot: ReleaseSnapshot;
342
+ issues: SiteIssue[];
343
+ /** Drafts whose props a migration changed: a store saves these before it publishes. */
344
+ migratedPages: {
345
+ id: string;
346
+ doc: PageDoc;
347
+ }[];
348
+ migratedSite: SiteDoc | null;
349
+ brandHash: string | null;
350
+ }
351
+ /**
352
+ * The publish pipeline every store shares: migrate every node, freeze the
353
+ * drafts into a snapshot, and check it. Throws `PublishBlockedError` when a
354
+ * check fails. Pages come in the order they are listed.
355
+ *
356
+ * @stability Beta
357
+ */
358
+ declare function prepareRelease(site: SiteDoc, pages: readonly {
359
+ id: string;
360
+ path: string;
361
+ kind: PageKind;
362
+ doc: PageDoc;
363
+ }[], options?: PublishOptions): PreparedRelease;
364
+ /** The cache tag every response of a site carries (`Vercel-Cache-Tag`). */
365
+ declare function siteCacheTag(siteId: string): string;
366
+ /** The cache tag of every page that embeds a form: purge it when the host changes that form. */
367
+ declare function formCacheTag(slug: string): string;
368
+ /** Why a site's cache is being purged. */
369
+ interface InvalidationInfo {
370
+ siteId: string;
371
+ /** `post`: a post was published, taken down, deleted or imported. */
372
+ action: "publish" | "rollback" | "post";
373
+ }
374
+ /**
375
+ * A store that purges the site's cached pages after every publish and
376
+ * rollback: `invalidate` gets `["site:<id>"]` and what happened.
377
+ *
378
+ * Mind what your CDN's purge does. On Vercel, `invalidateByTag` marks pages
379
+ * stale, so each cached URL serves the old release once more while it
380
+ * regenerates (no stampede); `dangerouslyDeleteByTag` drops them, so the
381
+ * next request waits for a render. Use the delete for a rollback that takes
382
+ * something down:
383
+ *
384
+ * withCacheInvalidation(store, (tags, { action }) =>
385
+ * action === "rollback" ? dangerouslyDeleteByTag(tags) : invalidateByTag(tags));
386
+ *
387
+ * Archiving a site goes through your own server or the database (the
388
+ * `genesis_sites` notification), so purge there too. A failed purge is
389
+ * logged and rethrown, so the editor shows that the live site may be stale.
390
+ *
391
+ * @stability Beta
392
+ */
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
+ };
408
+ interface NewSite {
409
+ id?: string;
410
+ /** The site document (defaults applied). */
411
+ site: SiteDocInput;
412
+ pages: {
413
+ id?: string;
414
+ path: string;
415
+ kind?: PageKind;
416
+ doc: PageDoc;
417
+ }[];
418
+ }
419
+ interface MemorySiteStore extends SiteStore {
420
+ createSite(input: NewSite): Promise<{
421
+ siteId: string;
422
+ pageIds: string[];
423
+ }>;
424
+ /** Archive (or bring back) a site: an archived site serves nothing. */
425
+ setStatus(siteId: string, status: "draft" | "live" | "archived"): Promise<void>;
426
+ }
427
+ interface MemoryStoreOptions {
428
+ reserved?: ReservedPaths;
429
+ /** Who may edit (default: everyone). Mirrors the database's `can_edit` hook, for tests. */
430
+ canEdit?: (siteId: string) => boolean;
431
+ now?: () => Date;
432
+ }
433
+ /**
434
+ * An in-memory `SiteStore`: the same rules as the database (compare-and-swap
435
+ * revisions, path checks, redirects on move, immutable releases), for tests,
436
+ * demos and local previews. Documents are copied in and out, so callers
437
+ * can't change stored state by mutating what they hold.
438
+ *
439
+ * @stability Beta
440
+ */
441
+ declare function createMemorySiteStore(options?: MemoryStoreOptions): MemorySiteStore;
442
+
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 };
@@ -0,0 +1,41 @@
1
+ import { SupabaseClient } from '@supabase/supabase-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';
5
+ import 'react';
6
+ import 'zod';
7
+ import '@marktiderman/genesis-design-system';
8
+
9
+ /**
10
+ * @marktiderman/genesis-sites/supabase: `createSupabaseSiteStore`, the
11
+ * `SiteStore` over the `genesis_sites` schema (migration
12
+ * `migrations/0001_genesis_sites.sql`).
13
+ *
14
+ * Pass a client signed in as the editor for drafts and publishing (row level
15
+ * security and `can_edit` / `can_view` decide), or an anonymous one for the
16
+ * public read (`getLive`, through `genesis_sites.live_release`). The schema
17
+ * must be exposed to the API.
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
22
+ declare const SITES_SCHEMA = "genesis_sites";
23
+
24
+ interface SupabaseSiteStoreOptions {
25
+ /** The storage bucket for `putAsset`. Default `site-assets`. */
26
+ bucket?: string;
27
+ }
28
+ /** A database error with its SQLSTATE (`GS409` a conflict, `42501` not allowed). */
29
+ declare class SiteStoreError extends Error {
30
+ readonly code: string | undefined;
31
+ readonly detail?: string | undefined;
32
+ constructor(message: string, code: string | undefined, detail?: string | undefined);
33
+ }
34
+ /**
35
+ * A `SiteStore` on Supabase.
36
+ *
37
+ * @stability Beta
38
+ */
39
+ declare function createSupabaseSiteStore(client: SupabaseClient, options?: SupabaseSiteStoreOptions): SiteStore;
40
+
41
+ export { SITES_SCHEMA, SiteStoreError, type SupabaseSiteStoreOptions, createSupabaseSiteStore };