@distinctagency/cms-client 1.24.0 → 1.26.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/version.ts","../src/queries.ts","../src/telemetry.ts","../src/embed.ts","../src/shop.ts","../src/events.ts","../src/maps.ts","../src/cdn.ts","../src/date.ts","../src/webhooks.ts"],"sourcesContent":["// Server-safe exports — no React, no \"use client\"\nexport { CMS_CLIENT_VERSION } from \"./version\"\nexport { createCmsClient, TRACKING_CONFIG_TAG, MAPBOX_CONFIG_TAG, singletonTag } from \"./queries\"\nexport type { TrackingConfig, TrackingConfigOptions, MapboxConfig, MapboxConfigOptions } from \"./queries\"\nexport { getEmbedHtml } from \"./embed\"\nexport { createShopClient } from \"./shop\"\nexport { createEventsClient } from \"./events\"\nexport { createMapsClient } from \"./maps\"\nexport type { MapsClientOptions } from \"./maps\"\nexport type {\n CmsEvent,\n CmsTicketTier,\n EventWithTiers,\n EventQueryOptions,\n CreateBookingParams,\n CreateBookingResult,\n EventStatus,\n BookingStatus,\n} from \"./events\"\nexport { getTransformUrl, getSrcSet, IMAGE_PRESETS } from \"./cdn\"\nexport type { ImageTransformOptions } from \"./cdn\"\nexport { parsePartialDate, formatPartialDate } from \"./date\"\nexport type { ParsedPartialDate, FormatPartialDateOptions } from \"./date\"\nexport { verifyWebhookSignature, revalidateAllTags, WEBHOOK_EVENTS } from \"./webhooks\"\nexport type { WebhookEvent, WebhookEventPayload } from \"./webhooks\"\nexport type {\n FieldType,\n FieldDefinition,\n DateGranularity,\n Tenant,\n Profile,\n ContentType,\n ContentItem,\n MediaItem,\n MediaFolder,\n ContentQueryOptions,\n CmsClientOptions,\n TenantMembership,\n ImageConfig,\n ContentTypeSeoConfig,\n Product,\n ProductStatus,\n ProductCategory,\n ProductVariant,\n ProductOption,\n OrderAddress,\n OrderStatus,\n PaymentStatus,\n OrderLineItem,\n DiscountType,\n CreateOrderParams,\n CreateOrderResult,\n ProductQueryOptions,\n MembershipTier,\n Member,\n GoogleReview,\n ReviewQueryOptions,\n EmbedValue,\n FlipbookPagePublic,\n FlipbookTocEntryPublic,\n FlipbookPublic,\n GeocodeOptions,\n GeocodeResult,\n} from \"./types\"\n","/**\n * The version of @distinctagency/cms-client embedded in this build.\n *\n * Bump this in lock-step with package.json — the value is read by the SDK to\n * report installed-version telemetry and to send `x-cms-client-version` on\n * outgoing requests.\n */\nexport const CMS_CLIENT_VERSION = \"1.24.0\"\n","import { createClient as createSupabaseClient } from \"@supabase/supabase-js\"\nimport type { SupabaseClient } from \"@supabase/supabase-js\"\nimport type {\n CmsClientOptions,\n ContentItem,\n ContentQueryOptions,\n ContentType,\n GoogleReview,\n ReviewQueryOptions,\n} from \"./types\"\nimport { CMS_CLIENT_VERSION } from \"./version\"\nimport { reportClientVersion } from \"./telemetry\"\n\n/**\n * Creates a CMS query client authenticated with a tenant API key.\n *\n * Usage:\n * ```ts\n * const cms = createCmsClient(supabase, { apiKey: process.env.CMS_API_KEY! })\n * const events = await cms.getContentItems('events', { status: 'published' })\n * ```\n *\n * The API key is sent as an `x-cms-api-key` header on every request.\n * Supabase RLS policies validate it against the tenant's stored key.\n */\nexport function createCmsClient(\n supabase: SupabaseClient,\n options: CmsClientOptions\n) {\n const { apiKey } = options\n\n // Create a new Supabase client with the API key header injected globally\n const client = withApiKey(supabase, apiKey) as SupabaseClient\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, options.appUrl)\n\n /** Resolve the tenant ID from the API key (cached per request) */\n let tenantIdCache: string | null = null\n\n async function getTenantId(): Promise<string> {\n if (tenantIdCache) return tenantIdCache\n\n const { data, error } = await client\n .from(\"tenants\")\n .select(\"id\")\n .single()\n\n if (error || !data) {\n throw new Error(\n \"Invalid CMS API key — no tenant found. Check your apiKey.\"\n )\n }\n\n tenantIdCache = data.id\n return data.id\n }\n\n /** Resolve a content type from its slug (cached) */\n const contentTypeCache = new Map<string, ContentType>()\n\n async function getContentTypeBySlug(contentTypeSlug: string): Promise<ContentType> {\n if (contentTypeCache.has(contentTypeSlug)) return contentTypeCache.get(contentTypeSlug)!\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"content_types\")\n .select(\"*\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", contentTypeSlug)\n .single()\n\n if (error || !data) {\n throw new Error(`Content type not found: ${contentTypeSlug}`)\n }\n\n const ct = data as ContentType\n contentTypeCache.set(contentTypeSlug, ct)\n return ct\n }\n\n async function getContentTypeId(contentTypeSlug: string): Promise<string> {\n const ct = await getContentTypeBySlug(contentTypeSlug)\n return ct.id\n }\n\n /** Check if a member token has access to a gated content type */\n async function checkMemberAccess(\n contentType: ContentType,\n memberToken?: string\n ): Promise<{ allowed: boolean; memberTierId: string | null }> {\n const requiredTierId = contentType.required_membership_tier_id\n if (!requiredTierId) return { allowed: true, memberTierId: null } // Public\n\n if (!memberToken) return { allowed: false, memberTierId: null } // Gated but no token\n\n // Verify the member's token and check their tier\n const { data: session } = await client\n .from(\"member_sessions\")\n .select(\"member_id\")\n .eq(\"token_hash\", memberToken) // Note: caller should hash the token\n .single()\n\n if (!session) return { allowed: false, memberTierId: null }\n\n const { data: member } = await client\n .from(\"members\")\n .select(\"membership_tier_id, status\")\n .eq(\"id\", session.member_id)\n .single()\n\n if (!member || member.status !== \"active\") return { allowed: false, memberTierId: null }\n\n // Check if the member's tier matches (or exceeds) the required tier\n // For now: exact match or the member has the required tier\n return {\n allowed: member.membership_tier_id === requiredTierId,\n memberTierId: member.membership_tier_id as string | null,\n }\n }\n\n async function getFlipbook(id: string): Promise<import(\"./types\").FlipbookPublic | null> {\n const { data, error } = await client\n .from(\"flipbooks\")\n .select(\"id, title, page_count, status, manifest, download_enabled, tenant_id\")\n .eq(\"id\", id)\n .single()\n if (error || !data) return null\n\n const manifest = data.manifest as\n | { pages: Array<{ image: string; thumb: string; w: number; h: number }>; toc: Array<{ title: string; page: number }> }\n | null\n if (!manifest) {\n return {\n id: data.id as string,\n title: data.title as string,\n page_count: (data.page_count as number) ?? 0,\n status: data.status as \"pending_upload\" | \"pending\" | \"processing\" | \"ready\" | \"failed\",\n page_images: [],\n toc: [],\n download_url: null,\n }\n }\n\n // Browser-safe env lookup via globalThis. The client package builds without\n // @types/node so referring to `process` directly fails the dts build; reading\n // through globalThis sidesteps that and works in every JS environment that\n // matters (Node, browsers, edge runtimes).\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n const cdnBase = proc?.env?.NEXT_PUBLIC_R2_PUBLIC_URL ?? \"https://cdn.distinctstudio.co.nz\"\n const prefix = `flipbooks/${data.tenant_id as string}/${data.id as string}/`\n return {\n id: data.id as string,\n title: data.title as string,\n page_count: (data.page_count as number) ?? manifest.pages.length,\n status: data.status as \"pending\" | \"processing\" | \"ready\" | \"failed\",\n page_images: manifest.pages.map((p) => ({\n url: `${cdnBase}/${prefix}${p.image}`,\n thumb_url: `${cdnBase}/${prefix}${p.thumb}`,\n w: p.w,\n h: p.h,\n })),\n toc: manifest.toc,\n download_url: data.download_enabled\n ? `${options.appUrl ?? \"\"}/api/flipbooks/${data.id as string}/download`\n : null,\n }\n }\n\n return {\n /**\n * List content items for a content type.\n * Defaults to published items ordered by most recent.\n */\n async getContentItems(\n contentTypeSlug: string,\n options: ContentQueryOptions = {}\n ): Promise<(ContentItem & { locked?: boolean })[]> {\n const contentType = await getContentTypeBySlug(contentTypeSlug)\n\n const {\n status = \"published\",\n orderBy = \"published_at\",\n orderDirection = \"desc\",\n limit = 100,\n offset = 0,\n memberToken,\n } = options\n\n // Check membership access\n const access = await checkMemberAccess(contentType, memberToken)\n\n // If gated and no access, check gating mode\n if (!access.allowed && contentType.required_membership_tier_id) {\n // Get tenant settings for gating mode\n const tenantId = await getTenantId()\n const { data: settings } = await client\n .from(\"tenant_settings\")\n .select(\"membership_gating_mode\")\n .eq(\"tenant_id\", tenantId)\n .single()\n\n const mode = (settings?.membership_gating_mode as string) ?? \"teaser\"\n\n if (mode === \"hide\") {\n return [] // Hide: return nothing\n }\n\n // Teaser mode: return items with locked flag, no body/data\n let query = client\n .from(\"content_items\")\n .select(\"id, title, slug, status, excerpt, seo_title, seo_description, featured_image, published_at, created_at, updated_at\")\n .eq(\"content_type_id\", contentType.id)\n\n if (status) query = query.eq(\"status\", status)\n query = query\n .order(orderBy, { ascending: orderDirection === \"asc\" })\n .range(offset, offset + limit - 1)\n\n const { data, error } = await query\n if (error) throw new Error(`Failed to fetch ${contentTypeSlug}: ${error.message}`)\n\n return (data ?? []).map((item) => ({\n ...item,\n data: {},\n locked: true,\n })) as (ContentItem & { locked: boolean })[]\n }\n\n // Full access\n let query = client\n .from(\"content_items\")\n .select(\"*\")\n .eq(\"content_type_id\", contentType.id)\n\n if (status) {\n query = query.eq(\"status\", status)\n }\n\n query = query\n .order(orderBy, { ascending: orderDirection === \"asc\" })\n .range(offset, offset + limit - 1)\n\n const { data, error } = await query\n\n if (error) {\n throw new Error(`Failed to fetch ${contentTypeSlug}: ${error.message}`)\n }\n\n return (data ?? []) as ContentItem[]\n },\n\n /**\n * Get a single content item by its slug.\n * Returns null if not found.\n */\n async getContentItemBySlug(\n contentTypeSlug: string,\n itemSlug: string\n ): Promise<ContentItem | null> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"content_items\")\n .select(\"*\")\n .eq(\"content_type_id\", contentTypeId)\n .eq(\"slug\", itemSlug)\n .single()\n\n if (error) {\n if (error.code === \"PGRST116\") return null // not found\n throw new Error(\n `Failed to fetch ${contentTypeSlug}/${itemSlug}: ${error.message}`\n )\n }\n\n return data as ContentItem\n },\n\n /**\n * Get the single published item for a singleton content type (\"page\").\n *\n * A singleton content type owns exactly one item. Use this for editable\n * page copy (hero text, section headings, etc.) stored in the CMS.\n * Returns null when the singleton has no published item yet — callers\n * should fall back to their hardcoded defaults in that case.\n *\n * Wrap this in `unstable_cache` with a `cms:singleton:<slug>` tag on the\n * consuming site to cache it and revalidate via the CMS webhook.\n */\n async getSingleton(contentTypeSlug: string): Promise<ContentItem | null> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"content_items\")\n .select(\"*\")\n .eq(\"content_type_id\", contentTypeId)\n .eq(\"status\", \"published\")\n .order(\"updated_at\", { ascending: false })\n .limit(1)\n .maybeSingle()\n\n if (error) {\n throw new Error(\n `Failed to fetch singleton ${contentTypeSlug}: ${error.message}`\n )\n }\n\n return (data as ContentItem) ?? null\n },\n\n /**\n * Get a content type definition (including its field schema).\n */\n async getContentType(contentTypeSlug: string): Promise<ContentType> {\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"content_types\")\n .select(\"*\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", contentTypeSlug)\n .single()\n\n if (error || !data) {\n throw new Error(`Content type not found: ${contentTypeSlug}`)\n }\n\n return data as ContentType\n },\n\n /**\n * Get all slugs for a content type (for generateStaticParams).\n */\n async getAllSlugs(\n contentTypeSlug: string\n ): Promise<{ slug: string }[]> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"content_items\")\n .select(\"slug\")\n .eq(\"content_type_id\", contentTypeId)\n .eq(\"status\", \"published\")\n\n if (error) {\n throw new Error(\n `Failed to fetch slugs for ${contentTypeSlug}: ${error.message}`\n )\n }\n\n return (data ?? []) as { slug: string }[]\n },\n\n /**\n * List all content types for this tenant.\n */\n async getContentTypes(): Promise<ContentType[]> {\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"content_types\")\n .select(\"*\")\n .eq(\"tenant_id\", tenantId)\n .order(\"name\")\n\n if (error) {\n throw new Error(`Failed to fetch content types: ${error.message}`)\n }\n\n return (data ?? []) as ContentType[]\n },\n\n /**\n * Get all slug redirects for a content type.\n * Use in next.config.js redirects() or middleware for 301s.\n * Returns: [{ old_slug, new_slug }, ...]\n */\n async getRedirects(\n contentTypeSlug: string\n ): Promise<{ old_slug: string; new_slug: string }[]> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"slug_redirects\")\n .select(\"old_slug, new_slug\")\n .eq(\"content_type_id\", contentTypeId)\n\n if (error) {\n throw new Error(\n `Failed to fetch redirects for ${contentTypeSlug}: ${error.message}`\n )\n }\n\n return (data ?? []) as { old_slug: string; new_slug: string }[]\n },\n\n /**\n * Get all custom path redirects for this tenant.\n * Use alongside getRedirects() in next.config.ts for full redirect coverage.\n */\n async getCustomRedirects(): Promise<\n { source_path: string; destination_path: string; permanent: boolean }[]\n > {\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"custom_redirects\")\n .select(\"source_path, destination_path, permanent\")\n .eq(\"tenant_id\", tenantId)\n\n if (error) {\n throw new Error(`Failed to fetch custom redirects: ${error.message}`)\n }\n\n return (data ?? []) as {\n source_path: string\n destination_path: string\n permanent: boolean\n }[]\n },\n\n /**\n * Get form submissions for a specific form.\n * Useful for displaying testimonials, reviews, etc.\n */\n async getFormSubmissions(\n formSlug: string,\n options: { status?: string; limit?: number; offset?: number } = {}\n ): Promise<Record<string, unknown>[]> {\n const tenantId = await getTenantId()\n\n const { data: form } = await client\n .from(\"forms\")\n .select(\"id\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", formSlug)\n .single()\n\n if (!form) return []\n\n const { limit = 50, offset = 0, status } = options\n\n let query = client\n .from(\"form_submissions\")\n .select(\"*\")\n .eq(\"form_id\", form.id)\n .eq(\"is_spam\", false)\n .order(\"created_at\", { ascending: false })\n .range(offset, offset + limit - 1)\n\n if (status) {\n query = query.eq(\"status\", status)\n }\n\n const { data } = await query\n return (data ?? []) as Record<string, unknown>[]\n },\n\n /**\n * Get a form configuration by slug.\n */\n async getForm(\n formSlug: string\n ): Promise<Record<string, unknown> | null> {\n const tenantId = await getTenantId()\n\n const { data } = await client\n .from(\"forms\")\n .select(\"id, name, slug, description, is_active\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", formSlug)\n .single()\n\n return data as Record<string, unknown> | null\n },\n\n /**\n * Get a flipbook by ID, including CDN-resolved page image URLs.\n * Returns null if not found or manifest not yet ready.\n */\n getFlipbook,\n\n /**\n * Get Google Reviews for this tenant.\n * Defaults to approved reviews ordered by most recent.\n */\n async getReviews(\n options: ReviewQueryOptions = {}\n ): Promise<GoogleReview[]> {\n const tenantId = await getTenantId()\n\n const {\n status = \"approved\",\n minRating,\n limit = 50,\n offset = 0,\n orderBy = \"review_timestamp\",\n orderDirection = \"desc\",\n } = options\n\n let query = client\n .from(\"google_reviews\")\n .select(\"id, author_name, author_photo_url, rating, text, review_timestamp\")\n .eq(\"tenant_id\", tenantId)\n\n if (status) {\n query = query.eq(\"status\", status)\n }\n\n if (minRating) {\n query = query.gte(\"rating\", minRating)\n }\n\n query = query\n .order(orderBy, { ascending: orderDirection === \"asc\" })\n .range(offset, offset + limit - 1)\n\n const { data, error } = await query\n\n if (error) {\n throw new Error(`Failed to fetch reviews: ${error.message}`)\n }\n\n return (data ?? []) as GoogleReview[]\n },\n\n /**\n * Get the tenant's third-party tracking IDs (Google Analytics, Google Tag Manager, Meta Pixel).\n * Each value is null when not configured. These IDs are public and safe to render in HTML.\n *\n * On Next.js, the result is cached and revalidated every hour by default\n * (`revalidate: 3600`) and tagged with `TRACKING_CONFIG_TAG`. Tenant sites\n * that want zero-lag updates can call `revalidateTag(TRACKING_CONFIG_TAG)`\n * from a webhook. Pass `revalidate: 0` to disable caching, `revalidate: false`\n * to cache indefinitely (tag-only invalidation), or any positive integer\n * (seconds) to override the interval. Outside Next.js the cache hints are\n * silently ignored — every call hits the network.\n */\n async getTrackingConfig(options: TrackingConfigOptions = {}): Promise<TrackingConfig> {\n const { revalidate = 3600, tags = [TRACKING_CONFIG_TAG] } = options\n const supabaseUrl = (supabase as unknown as { supabaseUrl: string }).supabaseUrl\n const supabaseKey = (supabase as unknown as { supabaseKey: string }).supabaseKey\n\n // Direct PostgREST fetch (instead of going through supabase-js) so that\n // Next.js sees the request and applies its `next` cache options. RLS\n // restricts the result to the tenant matching `x-cms-api-key`, so no\n // explicit tenant_id filter is needed.\n const url =\n `${supabaseUrl}/rest/v1/tenant_settings` +\n `?select=google_analytics_id,google_tag_manager_id,google_ads_id,meta_pixel_id&limit=1`\n\n const init: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } } = {\n headers: {\n apikey: supabaseKey,\n Authorization: `Bearer ${supabaseKey}`,\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n Accept: \"application/json\",\n },\n next: { revalidate, tags },\n }\n\n let row: {\n google_analytics_id: string | null\n google_tag_manager_id: string | null\n google_ads_id: string | null\n meta_pixel_id: string | null\n } | undefined\n\n try {\n const res = await fetch(url, init as RequestInit)\n if (res.ok) {\n const rows = (await res.json()) as Array<typeof row>\n row = rows?.[0]\n }\n } catch {\n // Network errors fall through to the all-null result so a transient\n // outage on the CMS never breaks page renders on the tenant site.\n }\n\n return {\n googleAnalyticsId: nullify(row?.google_analytics_id),\n googleTagManagerId: nullify(row?.google_tag_manager_id),\n googleAdsId: nullify(row?.google_ads_id),\n metaPixelId: nullify(row?.meta_pixel_id),\n }\n },\n\n /**\n * Get the tenant's Mapbox **public** token (`pk.*`) for rendering maps in the\n * browser. Returns `{ publicToken: null }` when not configured. The Mapbox\n * **secret** token is never exposed by the CMS — read it from your own\n * environment variables.\n *\n * Caching mirrors `getTrackingConfig()`: revalidated hourly by default and\n * tagged with `MAPBOX_CONFIG_TAG`. Call `revalidateTag(MAPBOX_CONFIG_TAG)`\n * from a `settings.mapbox_updated` webhook for zero-lag updates.\n */\n async getMapboxConfig(options: MapboxConfigOptions = {}): Promise<MapboxConfig> {\n const { revalidate = 3600, tags = [MAPBOX_CONFIG_TAG] } = options\n const supabaseUrl = (supabase as unknown as { supabaseUrl: string }).supabaseUrl\n const supabaseKey = (supabase as unknown as { supabaseKey: string }).supabaseKey\n\n const url =\n `${supabaseUrl}/rest/v1/tenant_settings` +\n `?select=mapbox_public_token&limit=1`\n\n const init: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } } = {\n headers: {\n apikey: supabaseKey,\n Authorization: `Bearer ${supabaseKey}`,\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n Accept: \"application/json\",\n },\n next: { revalidate, tags },\n }\n\n let row: { mapbox_public_token: string | null } | undefined\n\n try {\n const res = await fetch(url, init as RequestInit)\n if (res.ok) {\n const rows = (await res.json()) as Array<typeof row>\n row = rows?.[0]\n }\n } catch {\n // Network errors fall through to the null result so a transient CMS\n // outage never breaks map rendering on the tenant site.\n }\n\n return { publicToken: nullify(row?.mapbox_public_token) }\n },\n }\n}\n\n/**\n * Cache tag applied to `getTrackingConfig()` fetches on Next.js. Call\n * `revalidateTag(TRACKING_CONFIG_TAG)` from a webhook handler on the tenant\n * site to make tracking-ID changes take effect immediately rather than\n * waiting for the next revalidation interval.\n */\nexport const TRACKING_CONFIG_TAG = \"cms:tracking-config\"\n\nexport interface TrackingConfigOptions {\n /**\n * Cache lifetime for the underlying fetch on Next.js, in seconds.\n * Defaults to 3600 (one hour). Set to `0` to disable caching, or `false`\n * to cache indefinitely until the tag is revalidated. Ignored outside\n * Next.js runtimes.\n */\n revalidate?: number | false\n /**\n * Cache tags for the underlying fetch on Next.js. Defaults to\n * `[TRACKING_CONFIG_TAG]`. Override to namespace by tenant if you share a\n * single Next.js process across multiple tenants (uncommon).\n */\n tags?: string[]\n}\n\n/**\n * Cache tag applied to `getMapboxConfig()` fetches on Next.js. Call\n * `revalidateTag(MAPBOX_CONFIG_TAG)` from a `settings.mapbox_updated` webhook\n * handler so public-token changes take effect immediately.\n */\nexport const MAPBOX_CONFIG_TAG = \"cms:mapbox-config\"\n\n/**\n * Cache tag for a singleton \"page\" content type. Wrap `getSingleton(slug)` in\n * `unstable_cache` tagged with `singletonTag(slug)`, then call\n * `revalidateTag(singletonTag(slug))` from the CMS `content.updated` webhook\n * (the webhook payload's `cache_tags` already include this value).\n */\nexport function singletonTag(slug: string): string {\n return `cms:singleton:${slug}`\n}\n\nexport interface MapboxConfigOptions {\n /** Cache lifetime in seconds on Next.js. Defaults to 3600. `0` disables caching, `false` caches until tag revalidation. Ignored outside Next.js. */\n revalidate?: number | false\n /** Cache tags applied to the fetch. Defaults to `[MAPBOX_CONFIG_TAG]`. */\n tags?: string[]\n}\n\nexport interface MapboxConfig {\n /** The tenant's Mapbox public token (`pk.*`), or null when not configured. */\n publicToken: string | null\n}\n\nfunction nullify(v: string | null | undefined): string | null {\n if (!v) return null\n const trimmed = v.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport interface TrackingConfig {\n googleAnalyticsId: string | null\n googleTagManagerId: string | null\n googleAdsId: string | null\n metaPixelId: string | null\n}\n\n/**\n * Creates a new Supabase client that includes the `x-cms-api-key`\n * header in every request, using the same URL and key as the original.\n */\nfunction withApiKey(supabase: SupabaseClient, apiKey: string) {\n // Extract URL and key from the existing client\n const supabaseUrl = (supabase as unknown as { supabaseUrl: string }).supabaseUrl\n const supabaseKey = (supabase as unknown as { supabaseKey: string }).supabaseKey\n\n return createSupabaseClient(supabaseUrl, supabaseKey, {\n global: {\n headers: {\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n },\n },\n })\n}\n","import { CMS_CLIENT_VERSION } from \"./version\"\n\n/**\n * Reports the running SDK version to the CMS so the Super Admin UI can show\n * which client version each tenant site is on.\n *\n * Day-granular and process-local: at most one POST per (api key + day) per\n * Node process. Calls from browser code are ignored — the API key would be\n * exposed and the data is redundant with server-side reports.\n *\n * Fire-and-forget. Failures are silent — telemetry must never break a render.\n */\n\nconst DEFAULT_APP_URL = \"https://cms.distinctstudio.co.nz\"\n\ninterface ReportRecord {\n date: string // YYYY-MM-DD\n version: string\n}\n\nconst reported = new Map<string, ReportRecord>()\n\nfunction todayKey(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nexport function reportClientVersion(apiKey: string, appUrl?: string): void {\n // Skip in browser contexts — server-side calls already cover the tenant.\n if (typeof window !== \"undefined\") return\n if (!apiKey) return\n\n const today = todayKey()\n const last = reported.get(apiKey)\n if (last && last.date === today && last.version === CMS_CLIENT_VERSION) return\n\n // Optimistically mark as reported so concurrent calls don't all fire. If the\n // request fails we'll retry tomorrow — that's acceptable for telemetry.\n reported.set(apiKey, { date: today, version: CMS_CLIENT_VERSION })\n\n const envAppUrl =\n typeof process !== \"undefined\" ? process.env?.NEXT_PUBLIC_CMS_URL : undefined\n const base = (appUrl ?? envAppUrl ?? DEFAULT_APP_URL).replace(/\\/$/, \"\")\n const url = `${base}/api/client-telemetry`\n\n // Fire and forget. Use a hand-rolled then() chain rather than async/await so\n // we don't accidentally surface an unhandled rejection.\n void fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n },\n body: JSON.stringify({\n api_key: apiKey,\n version: CMS_CLIENT_VERSION,\n }),\n }).catch(() => {\n // Allow a retry on the next call by clearing the optimistic record.\n reported.delete(apiKey)\n })\n}\n","import type { EmbedValue } from \"./types\"\n\nfunction toCssAspectRatio(ratio: string): string {\n const parts = ratio.split(\":\")\n if (parts.length !== 2) return \"16/9\"\n const w = parseFloat(parts[0])\n const h = parseFloat(parts[1])\n if (!w || !h || w <= 0 || h <= 0) return \"16/9\"\n return `${w}/${h}`\n}\n\n/**\n * Generate responsive iframe HTML for an embed field value.\n * Returns an empty string if the value is invalid or the URL is not HTTPS.\n */\nexport function getEmbedHtml(value: unknown): string {\n if (!value || typeof value !== \"object\") return \"\"\n const embed = value as EmbedValue\n if (!embed.url || !embed.url.startsWith(\"https://\")) return \"\"\n\n const width = embed.width || \"100%\"\n const aspectRatio = toCssAspectRatio(embed.aspect_ratio || \"16:9\")\n\n return `<div style=\"position:relative;width:${width};aspect-ratio:${aspectRatio}\"><iframe src=\"${embed.url}\" style=\"position:absolute;inset:0;width:100%;height:100%\" frameborder=\"0\" sandbox=\"allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox\" allowfullscreen loading=\"lazy\"></iframe></div>`\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\"\nimport type {\n Product,\n ProductCategory,\n ProductQueryOptions,\n CreateOrderParams,\n CreateOrderResult,\n} from \"./types\"\nimport { CMS_CLIENT_VERSION } from \"./version\"\nimport { reportClientVersion } from \"./telemetry\"\n\nconst DEFAULT_APP_URL = \"https://cms.distinctstudio.co.nz\"\n\nexport interface ShopClientOptions {\n /** Tenant API key (UUID). Sent as `x-cms-api-key` on every request. */\n apiKey: string\n /** CMS app base URL. Defaults to `https://cms.distinctstudio.co.nz`. */\n appUrl?: string\n}\n\n/**\n * Creates a typed client for the eCommerce REST API (products, categories, orders).\n *\n * Unlike `createCmsClient`, the shop client talks to the CMS REST endpoints\n * (`/api/products`, `/api/product-categories`, `/api/orders/create`) rather than\n * Supabase directly — it doesn't need the `supabase` argument, but accepts it\n * for API symmetry with the rest of the SDK.\n *\n * Usage:\n * ```ts\n * const shop = createShopClient(supabase, { apiKey: process.env.CMS_API_KEY! })\n * const products = await shop.getProducts({ category: \"electronics\", limit: 20 })\n * ```\n */\nexport function createShopClient(\n _supabase: SupabaseClient,\n options: ShopClientOptions\n) {\n const { apiKey } = options\n const appUrl = (options.appUrl ?? DEFAULT_APP_URL).replace(/\\/$/, \"\")\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, appUrl)\n\n function authHeaders(): HeadersInit {\n return {\n \"Content-Type\": \"application/json\",\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n }\n }\n\n async function getJson<T>(path: string): Promise<T> {\n const res = await fetch(`${appUrl}${path}`, {\n headers: authHeaders(),\n // Caller controls Next.js caching by wrapping the call site.\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}) as { error?: string })\n throw new Error(\n body.error ?? `Shop request failed (${res.status}): ${path}`\n )\n }\n return res.json() as Promise<T>\n }\n\n return {\n /**\n * List published products. Includes nested `variants` and `options`.\n *\n * `category` filters by category slug (matches the FK `category_id`,\n * with a fallback to the legacy `category` text column).\n */\n async getProducts(queryOptions: ProductQueryOptions = {}): Promise<Product[]> {\n const params = new URLSearchParams()\n if (queryOptions.category) params.set(\"category\", queryOptions.category)\n if (queryOptions.tags?.length) params.set(\"tags\", queryOptions.tags.join(\",\"))\n if (queryOptions.limit) params.set(\"limit\", String(queryOptions.limit))\n if (queryOptions.offset) params.set(\"offset\", String(queryOptions.offset))\n if (queryOptions.sort) params.set(\"sort\", queryOptions.sort)\n if (queryOptions.order) params.set(\"order\", queryOptions.order)\n\n const qs = params.toString()\n const { products } = await getJson<{ products: Product[] }>(\n `/api/products${qs ? `?${qs}` : \"\"}`\n )\n return products ?? []\n },\n\n /**\n * Get a single published product by slug. Returns null if not found.\n * Includes nested `variants` and `options`.\n */\n async getProductBySlug(slug: string): Promise<Product | null> {\n const res = await fetch(`${appUrl}/api/products/${encodeURIComponent(slug)}`, {\n headers: authHeaders(),\n })\n if (res.status === 404) return null\n if (!res.ok) {\n const body = await res.json().catch(() => ({}) as { error?: string })\n throw new Error(body.error ?? `Failed to fetch product ${slug}`)\n }\n const { product } = (await res.json()) as { product: Product }\n return product ?? null\n },\n\n /**\n * List product categories for the tenant, ordered by sort_order then name.\n */\n async getCategories(): Promise<ProductCategory[]> {\n const { categories } = await getJson<{ categories: ProductCategory[] }>(\n `/api/product-categories`\n )\n return categories ?? []\n },\n\n /**\n * Get a single category by slug. Returns null if not found.\n */\n async getCategoryBySlug(slug: string): Promise<ProductCategory | null> {\n const all = await this.getCategories()\n return all.find((c) => c.slug === slug) ?? null\n },\n\n /**\n * Create a pending order and return a Stripe PaymentIntent client_secret\n * to confirm payment client-side.\n *\n * Stripe must be configured for the tenant; eCommerce must be enabled on\n * their billing plan. Validates stock for tracked variants and applies\n * any discount code before creating the PaymentIntent.\n */\n async createOrder(params: CreateOrderParams): Promise<CreateOrderResult> {\n const res = await fetch(`${appUrl}/api/orders/create`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ api_key: apiKey, ...params }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}) as { error?: string })\n throw new Error(body.error ?? \"Order creation failed\")\n }\n return res.json() as Promise<CreateOrderResult>\n },\n }\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\"\nimport { reportClientVersion } from \"./telemetry\"\n\nexport type EventStatus = \"draft\" | \"published\" | \"archived\"\nexport type BookingStatus = \"pending\" | \"confirmed\" | \"cancelled\" | \"refunded\"\n\nexport interface CmsEvent {\n id: string\n tenant_id: string\n title: string\n slug: string\n description: string | null\n short_description: string | null\n status: EventStatus\n start_at: string\n end_at: string | null\n venue_name: string | null\n venue_address: string | null\n hero_image: string | null\n gallery: string[]\n tags: string[]\n seo_title: string | null\n seo_description: string | null\n metadata: Record<string, unknown>\n sort_order: number\n published_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface CmsTicketTier {\n id: string\n event_id: string\n tenant_id: string\n name: string\n description: string | null\n price_cents: number\n capacity: number | null\n sold_count: number\n sales_start_at: string | null\n sales_end_at: string | null\n is_active: boolean\n sort_order: number\n}\n\nexport interface EventWithTiers extends CmsEvent {\n tiers: CmsTicketTier[]\n}\n\nexport interface EventQueryOptions {\n /** Only include events with start_at >= now. Default false. */\n upcomingOnly?: boolean\n tag?: string\n limit?: number\n offset?: number\n sort?: \"start_at\" | \"sort_order\" | \"created_at\"\n order?: \"asc\" | \"desc\"\n}\n\nexport interface CreateBookingParams {\n event_slug: string\n ticket_tier_id?: string\n customer_email: string\n customer_name?: string\n customer_phone?: string\n quantity?: number\n success_url?: string\n cancel_url?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CreateBookingResult {\n booking_id: string\n booking_number: string\n status: \"pending\" | \"confirmed\"\n /** Redirect the browser here for paid bookings. */\n checkout_url?: string\n /** Present on free bookings — used for attendance QR display. */\n qr_token?: string\n}\n\nexport function createEventsClient(\n supabase: SupabaseClient,\n options: { apiKey: string; appUrl?: string }\n) {\n const { apiKey, appUrl } = options\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, appUrl)\n\n async function getEvents(\n queryOptions?: EventQueryOptions\n ): Promise<EventWithTiers[]> {\n let query = supabase\n .from(\"events\")\n .select(\"*, tiers:ticket_tiers(*)\")\n .eq(\"status\", \"published\")\n .order(queryOptions?.sort ?? \"start_at\", {\n ascending: (queryOptions?.order ?? \"asc\") === \"asc\",\n })\n\n if (queryOptions?.upcomingOnly) {\n query = query.gte(\"start_at\", new Date().toISOString())\n }\n if (queryOptions?.tag) {\n query = query.contains(\"tags\", [queryOptions.tag])\n }\n if (queryOptions?.limit) {\n query = query.limit(queryOptions.limit)\n }\n if (queryOptions?.offset) {\n query = query.range(\n queryOptions.offset,\n queryOptions.offset + (queryOptions.limit ?? 50) - 1\n )\n }\n\n const { data } = await query\n return (data ?? []) as EventWithTiers[]\n }\n\n async function getEventBySlug(slug: string): Promise<EventWithTiers | null> {\n const { data } = await supabase\n .from(\"events\")\n .select(\"*, tiers:ticket_tiers(*)\")\n .eq(\"slug\", slug)\n .eq(\"status\", \"published\")\n .single()\n return (data as EventWithTiers) ?? null\n }\n\n async function createBooking(\n params: CreateBookingParams\n ): Promise<CreateBookingResult> {\n const baseUrl = appUrl ?? \"\"\n const res = await fetch(`${baseUrl}/api/bookings/create`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ api_key: apiKey, ...params }),\n })\n if (!res.ok) {\n const err = await res\n .json()\n .catch(() => ({ error: \"Booking creation failed\" }))\n throw new Error(err.error ?? \"Booking creation failed\")\n }\n return res.json() as Promise<CreateBookingResult>\n }\n\n return { getEvents, getEventBySlug, createBooking }\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\"\nimport type { GeocodeOptions, GeocodeResult } from \"./types\"\nimport { CMS_CLIENT_VERSION } from \"./version\"\nimport { reportClientVersion } from \"./telemetry\"\n\nconst DEFAULT_APP_URL = \"https://cms.distinctstudio.co.nz\"\n\nexport interface MapsClientOptions {\n /** Tenant API key (UUID). Sent as `x-cms-api-key` on every request. */\n apiKey: string\n /** CMS app base URL. Defaults to `https://cms.distinctstudio.co.nz`. */\n appUrl?: string\n}\n\n/**\n * Creates a typed client for Mapbox-backed helpers (forward geocoding, with\n * more to come). Like `createShopClient`, it talks to CMS REST endpoints rather\n * than Supabase directly — it doesn't need the `supabase` argument but accepts\n * it for API symmetry. The Mapbox call runs server-side with the tenant's\n * public token; the token is never exposed to the browser.\n *\n * ```ts\n * const maps = createMapsClient(supabase, { apiKey: process.env.CMS_API_KEY! })\n * const [best] = await maps.geocodeAddress(\"12 Queen St, Auckland\")\n * ```\n */\nexport function createMapsClient(_supabase: SupabaseClient, options: MapsClientOptions) {\n const { apiKey } = options\n const appUrl = (options.appUrl ?? DEFAULT_APP_URL).replace(/\\/$/, \"\")\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, appUrl)\n\n return {\n /**\n * Forward-geocode an address string. Returns matches ordered by relevance\n * (`results[0]` is the best match), or [] when nothing matches. Throws on a\n * configuration or upstream error.\n */\n async geocodeAddress(query: string, options: GeocodeOptions = {}): Promise<GeocodeResult[]> {\n const q = query.trim()\n if (!q) throw new Error(\"geocodeAddress: query is required\")\n\n const params = new URLSearchParams({ q })\n if (options.limit != null) params.set(\"limit\", String(options.limit))\n if (options.country) params.set(\"country\", options.country)\n if (options.proximity) params.set(\"proximity\", `${options.proximity[0]},${options.proximity[1]}`)\n if (options.types) params.set(\"types\", options.types)\n if (options.language) params.set(\"language\", options.language)\n\n const res = await fetch(`${appUrl}/api/mapbox/geocode?${params.toString()}`, {\n headers: {\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n },\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n throw new Error(body.error ?? `Geocoding failed (${res.status})`)\n }\n const data = (await res.json()) as { results?: GeocodeResult[] }\n return data.results ?? []\n },\n }\n}\n","/**\n * Image helpers for CMS content.\n *\n * Images are already optimised (WebP, max 2400px) on upload.\n * No server-side transforms needed — serve originals directly.\n *\n * These functions are kept for backward compatibility but no longer\n * call Supabase image transformation endpoints.\n */\n\nexport interface ImageTransformOptions {\n width?: number\n height?: number\n quality?: number\n resize?: \"contain\" | \"cover\" | \"fill\"\n format?: \"origin\"\n}\n\n/**\n * Returns the image URL directly.\n *\n * Previously this converted URLs to use Supabase's /render/image/\n * transform endpoint, but images are now pre-optimised on upload\n * (WebP, max 2400px, quality 82) so transforms are unnecessary.\n *\n * The function is kept for backward compatibility — existing code\n * that calls getTransformUrl() will continue to work without changes.\n */\nexport function getTransformUrl(\n originalUrl: string,\n _options: ImageTransformOptions = {}\n): string {\n return originalUrl\n}\n\n/**\n * Returns a simple srcSet using the original image.\n *\n * Previously generated multiple transformed widths, but since images\n * are now pre-optimised WebP, a single source is sufficient.\n * Modern browsers handle responsive display efficiently with a\n * well-sized WebP source.\n */\nexport function getSrcSet(\n originalUrl: string,\n _widths: number[] = [],\n _quality = 80\n): string {\n return `${originalUrl} 2400w`\n}\n\n/**\n * Common image size presets — kept for backward compatibility.\n * Since images are pre-optimised, these are informational only.\n */\nexport const IMAGE_PRESETS = {\n thumbnail: { width: 150, height: 150, resize: \"cover\" as const, quality: 70 },\n card: { width: 400, height: 300, resize: \"cover\" as const, quality: 80 },\n hero: { width: 1200, height: 630, resize: \"cover\" as const, quality: 85 },\n og: { width: 1200, height: 630, resize: \"cover\" as const, quality: 90 },\n avatar: { width: 80, height: 80, resize: \"cover\" as const, quality: 75 },\n full: { width: 1920, resize: \"contain\" as const, quality: 85 },\n} as const\n","import type { DateGranularity } from \"./types\"\n\nexport interface ParsedPartialDate {\n granularity: DateGranularity\n /** 4-digit year — absent for `month` and `day_month`. */\n year?: number\n /** 1-12 — absent for `year`. */\n month?: number\n /** 1-31 — present only for `full` and `day_month`. */\n day?: number\n}\n\n/**\n * Parses a partial-ISO date string (as produced by a `date` field) into its parts.\n * Returns `null` if the string doesn't match a known granularity shape.\n */\nexport function parsePartialDate(value: string): ParsedPartialDate | null {\n if (!value) return null\n let m: RegExpExecArray | null\n if ((m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value)))\n return { granularity: \"full\", year: +m[1], month: +m[2], day: +m[3] }\n if ((m = /^(\\d{4})-(\\d{2})$/.exec(value)))\n return { granularity: \"month_year\", year: +m[1], month: +m[2] }\n if ((m = /^(\\d{4})$/.exec(value))) return { granularity: \"year\", year: +m[1] }\n if ((m = /^--(\\d{2})-(\\d{2})$/.exec(value)))\n return { granularity: \"day_month\", month: +m[1], day: +m[2] }\n if ((m = /^--(\\d{2})$/.exec(value))) return { granularity: \"month\", month: +m[1] }\n return null\n}\n\nexport interface FormatPartialDateOptions {\n /** BCP 47 locale(s) passed to Intl.DateTimeFormat. Defaults to the runtime locale. */\n locale?: string | string[]\n /** Month rendering style. Defaults to `long` (e.g. \"May\"). */\n month?: \"long\" | \"short\" | \"narrow\" | \"numeric\" | \"2-digit\"\n}\n\n/**\n * Formats a partial-ISO date string for display, honouring the value's granularity\n * and locale-correct part ordering (e.g. \"12 May\" vs \"May 12\"). Returns the raw\n * string unchanged if it isn't a recognised partial date.\n */\nexport function formatPartialDate(value: string, options: FormatPartialDateOptions = {}): string {\n const parsed = parsePartialDate(value)\n if (!parsed) return value\n\n const { locale, month = \"long\" } = options\n const monthIndex = parsed.month ? parsed.month - 1 : 0\n\n switch (parsed.granularity) {\n case \"year\":\n return String(parsed.year)\n case \"month\":\n return new Intl.DateTimeFormat(locale, { month }).format(new Date(2000, monthIndex, 1))\n case \"month_year\":\n return new Intl.DateTimeFormat(locale, { month, year: \"numeric\" }).format(\n new Date(parsed.year!, monthIndex, 1)\n )\n case \"day_month\":\n return new Intl.DateTimeFormat(locale, { month, day: \"numeric\" }).format(\n new Date(2000, monthIndex, parsed.day!)\n )\n case \"full\":\n return new Intl.DateTimeFormat(locale, { year: \"numeric\", month, day: \"numeric\" }).format(\n new Date(parsed.year!, monthIndex, parsed.day!)\n )\n }\n}\n","/**\n * Helpers for verifying outbound webhooks fired by the CMS.\n *\n * The CMS POSTs JSON to subscriber URLs and signs the raw body with\n * HMAC-SHA256 using the shared secret you set in the Webhooks tab. The hex\n * digest arrives in the `X-CMS-Signature` header.\n *\n * Use Web Crypto so this works in Node, Edge, Deno, and Bun runtimes.\n */\n\nexport interface WebhookEventPayload {\n event: string\n tenant_id: string\n content_type_slug?: string\n content_item_id?: string\n slug?: string\n title?: string\n status?: string\n /** Stable identifier of the resource that changed (e.g. product id, flipbook id, integration provider). */\n resource_id?: string\n /**\n * Cache tags the receiver should invalidate. Use {@link revalidateAllTags}\n * to wire this through Next.js's `revalidateTag()` in one line.\n * All tags are namespaced with the `cms:` prefix.\n */\n cache_tags?: string[]\n /** Free-form bag for event-specific extras (e.g. provider name, change counts). */\n data?: Record<string, unknown>\n timestamp: string\n /** Commerce + custom events may attach extra top-level fields. */\n [key: string]: unknown\n}\n\n/**\n * Verify the HMAC-SHA256 signature on a webhook delivery.\n *\n * Pass the **raw** request body (not a parsed JSON object) — re-stringifying\n * a parsed body can change whitespace and invalidate the signature.\n *\n * Returns `true` when the signature matches in constant time, `false`\n * otherwise. Never throws on a bad signature; only throws if the\n * `crypto.subtle` API is unavailable in the runtime.\n *\n * @example\n * const raw = await req.text()\n * const sig = req.headers.get(\"x-cms-signature\")\n * if (!await verifyWebhookSignature(process.env.CMS_WEBHOOK_SECRET!, raw, sig)) {\n * return new Response(\"bad signature\", { status: 401 })\n * }\n * const payload = JSON.parse(raw) as WebhookEventPayload\n */\nexport async function verifyWebhookSignature(\n secret: string,\n rawBody: string,\n signature: string | null | undefined\n): Promise<boolean> {\n if (!signature || !secret) return false\n\n const subtle = (globalThis.crypto && globalThis.crypto.subtle) || null\n if (!subtle) {\n throw new Error(\n \"verifyWebhookSignature requires the Web Crypto API (globalThis.crypto.subtle). \" +\n \"Available in Node 18+, all Edge runtimes, Deno, and Bun.\"\n )\n }\n\n const encoder = new TextEncoder()\n const key = await subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n )\n const sigBuffer = await subtle.sign(\"HMAC\", key, encoder.encode(rawBody))\n const expected = bufferToHex(sigBuffer)\n\n return timingSafeEqualHex(expected, signature.trim())\n}\n\n/**\n * The webhook event names the CMS can fire. Use as a discriminator on the\n * `event` field of {@link WebhookEventPayload}.\n *\n * Most receivers don't need to switch on the event name — every payload\n * carries a `cache_tags` array. Pass it to {@link revalidateAllTags} to\n * invalidate the right Next.js caches in one line.\n */\nexport const WEBHOOK_EVENTS = [\n // Content\n \"content.published\",\n \"content.unpublished\",\n \"content.updated\",\n \"content.deleted\",\n \"content_type.updated\",\n \"content_type.deleted\",\n // Settings (diffed — only fires when a relevant field actually changed)\n \"settings.tracking_updated\",\n \"settings.brand_updated\",\n \"settings.integration_updated\",\n \"settings.mapbox_updated\",\n // Commerce\n \"products.updated\",\n \"product_categories.updated\",\n \"ticket_tiers.updated\",\n \"membership_tiers.updated\",\n \"order.created\",\n \"order.paid\",\n \"order.payment_failed\",\n \"order.refunded\",\n \"order.shipped\",\n \"booking.confirmed\",\n \"inventory.low_stock\",\n // Site\n \"redirects.updated\",\n \"reviews.synced\",\n \"flipbook.ready\",\n] as const\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number]\n\n/**\n * Invalidate every cache tag listed in a webhook payload. Call this from your\n * receiver and most events will Just Work without per-event branching.\n *\n * Pass Next.js's `revalidateTag` (or any function with the same shape) — the\n * SDK has no peer dep on `next/cache`, so the import stays in your code.\n *\n * **Next 16 compatibility.** In Next 16, `revalidateTag(tag)` was made into\n * `revalidateTag(tag, profile)`, and calling it with one argument emits a\n * deprecation warning. The cache profile controls expiry — `\"default\"` and\n * `\"max\"` both have non-zero `expire` values and will **not** invalidate the\n * cache. Immediate invalidation requires `{ expire: 0 }`, which isn't\n * documented in the function reference. This helper always passes\n * `{ expire: 0 }` as the second argument, so it does the right thing on Next 16\n * and is silently ignored by Next 14/15.\n *\n * @example\n * import { revalidateTag } from \"next/cache\"\n * import { revalidateAllTags } from \"@distinctagency/cms-client\"\n *\n * export async function POST(req: Request) {\n * const raw = await req.text()\n * if (!await verifyWebhookSignature(SECRET, raw, req.headers.get(\"x-cms-signature\"))) {\n * return new Response(\"bad sig\", { status: 401 })\n * }\n * const payload = JSON.parse(raw) as WebhookEventPayload\n * revalidateAllTags(payload, revalidateTag)\n * return Response.json({ ok: true })\n * }\n */\nexport function revalidateAllTags(\n payload: Pick<WebhookEventPayload, \"cache_tags\">,\n revalidateTag: (tag: string, profile: { expire?: number }) => unknown\n): string[] {\n const tags = payload.cache_tags\n if (!Array.isArray(tags) || tags.length === 0) return []\n const invalidated: string[] = []\n for (const tag of tags) {\n if (typeof tag !== \"string\" || tag.length === 0) continue\n revalidateTag(tag, { expire: 0 })\n invalidated.push(tag)\n }\n return invalidated\n}\n\nfunction bufferToHex(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf)\n let out = \"\"\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i].toString(16).padStart(2, \"0\")\n }\n return out\n}\n\n/**\n * Constant-time equality check on two hex strings. Returns false fast when\n * lengths differ — the length itself is not secret.\n */\nfunction timingSafeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let mismatch = 0\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return mismatch === 0\n}\n"],"mappings":"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","names":["options","query","data","error","createSupabaseClient","DEFAULT_APP_URL","DEFAULT_APP_URL","options"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/version.ts","../src/queries.ts","../src/telemetry.ts","../src/embed.ts","../src/shop.ts","../src/events.ts","../src/maps.ts","../src/cdn.ts","../src/date.ts","../src/webhooks.ts","../src/visual-editing/protocol.ts"],"sourcesContent":["// Server-safe exports — no React, no \"use client\"\nexport { CMS_CLIENT_VERSION } from \"./version\"\nexport { createCmsClient, TRACKING_CONFIG_TAG, MAPBOX_CONFIG_TAG, singletonTag } from \"./queries\"\nexport type { TrackingConfig, TrackingConfigOptions, MapboxConfig, MapboxConfigOptions } from \"./queries\"\nexport { getEmbedHtml } from \"./embed\"\nexport { createShopClient } from \"./shop\"\nexport { createEventsClient } from \"./events\"\nexport { createMapsClient } from \"./maps\"\nexport type { MapsClientOptions } from \"./maps\"\nexport type {\n CmsEvent,\n CmsTicketTier,\n EventWithTiers,\n EventQueryOptions,\n CreateBookingParams,\n CreateBookingResult,\n EventStatus,\n BookingStatus,\n} from \"./events\"\nexport { getTransformUrl, getSrcSet, IMAGE_PRESETS } from \"./cdn\"\nexport type { ImageTransformOptions } from \"./cdn\"\nexport { parsePartialDate, formatPartialDate } from \"./date\"\nexport type { ParsedPartialDate, FormatPartialDateOptions } from \"./date\"\nexport { verifyWebhookSignature, revalidateAllTags, WEBHOOK_EVENTS } from \"./webhooks\"\nexport type { WebhookEvent, WebhookEventPayload } from \"./webhooks\"\nexport {\n CMS_VISUAL_SOURCE,\n DEFAULT_TRUSTED_ORIGINS,\n isTrustedOrigin,\n parseVisualMessage,\n} from \"./visual-editing/protocol\"\nexport type {\n VisualFieldType,\n VisualFieldMeta,\n VisualFieldSchema,\n ReadyMessage,\n EditMessage,\n PickImageMessage,\n InitMessage,\n SetMessage,\n BridgeOutbound,\n BridgeInbound,\n VisualMessage,\n} from \"./visual-editing/protocol\"\nexport type {\n FieldType,\n FieldDefinition,\n DateGranularity,\n Tenant,\n Profile,\n ContentType,\n ContentItem,\n MediaItem,\n MediaFolder,\n ContentQueryOptions,\n CmsClientOptions,\n TenantMembership,\n ImageConfig,\n ContentTypeSeoConfig,\n Product,\n ProductStatus,\n ProductCategory,\n ProductVariant,\n ProductOption,\n OrderAddress,\n OrderStatus,\n PaymentStatus,\n OrderLineItem,\n DiscountType,\n CreateOrderParams,\n CreateOrderResult,\n ProductQueryOptions,\n MembershipTier,\n Member,\n GoogleReview,\n ReviewQueryOptions,\n EmbedValue,\n FlipbookPagePublic,\n FlipbookTocEntryPublic,\n FlipbookPublic,\n GeocodeOptions,\n GeocodeResult,\n} from \"./types\"\n","/**\n * The version of @distinctagency/cms-client embedded in this build.\n *\n * Bump this in lock-step with package.json — the value is read by the SDK to\n * report installed-version telemetry and to send `x-cms-client-version` on\n * outgoing requests.\n */\nexport const CMS_CLIENT_VERSION = \"1.26.0\"\n","import { createClient as createSupabaseClient } from \"@supabase/supabase-js\"\nimport type { SupabaseClient } from \"@supabase/supabase-js\"\nimport type {\n CmsClientOptions,\n ContentItem,\n ContentQueryOptions,\n ContentType,\n GoogleReview,\n ReviewQueryOptions,\n} from \"./types\"\nimport { CMS_CLIENT_VERSION } from \"./version\"\nimport { reportClientVersion } from \"./telemetry\"\n\n/**\n * Creates a CMS query client authenticated with a tenant API key.\n *\n * Usage:\n * ```ts\n * const cms = createCmsClient(supabase, { apiKey: process.env.CMS_API_KEY! })\n * const events = await cms.getContentItems('events', { status: 'published' })\n * ```\n *\n * The API key is sent as an `x-cms-api-key` header on every request.\n * Supabase RLS policies validate it against the tenant's stored key.\n */\nexport function createCmsClient(\n supabase: SupabaseClient,\n options: CmsClientOptions\n) {\n const { apiKey } = options\n\n // Create a new Supabase client with the API key header injected globally\n const client = withApiKey(supabase, apiKey) as SupabaseClient\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, options.appUrl)\n\n /** Resolve the tenant ID from the API key (cached per request) */\n let tenantIdCache: string | null = null\n\n async function getTenantId(): Promise<string> {\n if (tenantIdCache) return tenantIdCache\n\n const { data, error } = await client\n .from(\"tenants\")\n .select(\"id\")\n .single()\n\n if (error || !data) {\n throw new Error(\n \"Invalid CMS API key — no tenant found. Check your apiKey.\"\n )\n }\n\n tenantIdCache = data.id\n return data.id\n }\n\n /** Resolve a content type from its slug (cached) */\n const contentTypeCache = new Map<string, ContentType>()\n\n async function getContentTypeBySlug(contentTypeSlug: string): Promise<ContentType> {\n if (contentTypeCache.has(contentTypeSlug)) return contentTypeCache.get(contentTypeSlug)!\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"content_types\")\n .select(\"*\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", contentTypeSlug)\n .single()\n\n if (error || !data) {\n throw new Error(`Content type not found: ${contentTypeSlug}`)\n }\n\n const ct = data as ContentType\n contentTypeCache.set(contentTypeSlug, ct)\n return ct\n }\n\n async function getContentTypeId(contentTypeSlug: string): Promise<string> {\n const ct = await getContentTypeBySlug(contentTypeSlug)\n return ct.id\n }\n\n /** Check if a member token has access to a gated content type */\n async function checkMemberAccess(\n contentType: ContentType,\n memberToken?: string\n ): Promise<{ allowed: boolean; memberTierId: string | null }> {\n const requiredTierId = contentType.required_membership_tier_id\n if (!requiredTierId) return { allowed: true, memberTierId: null } // Public\n\n if (!memberToken) return { allowed: false, memberTierId: null } // Gated but no token\n\n // Verify the member's token and check their tier\n const { data: session } = await client\n .from(\"member_sessions\")\n .select(\"member_id\")\n .eq(\"token_hash\", memberToken) // Note: caller should hash the token\n .single()\n\n if (!session) return { allowed: false, memberTierId: null }\n\n const { data: member } = await client\n .from(\"members\")\n .select(\"membership_tier_id, status\")\n .eq(\"id\", session.member_id)\n .single()\n\n if (!member || member.status !== \"active\") return { allowed: false, memberTierId: null }\n\n // Check if the member's tier matches (or exceeds) the required tier\n // For now: exact match or the member has the required tier\n return {\n allowed: member.membership_tier_id === requiredTierId,\n memberTierId: member.membership_tier_id as string | null,\n }\n }\n\n async function getFlipbook(id: string): Promise<import(\"./types\").FlipbookPublic | null> {\n const { data, error } = await client\n .from(\"flipbooks\")\n .select(\"id, title, page_count, status, manifest, download_enabled, tenant_id\")\n .eq(\"id\", id)\n .single()\n if (error || !data) return null\n\n const manifest = data.manifest as\n | { pages: Array<{ image: string; thumb: string; w: number; h: number }>; toc: Array<{ title: string; page: number }> }\n | null\n if (!manifest) {\n return {\n id: data.id as string,\n title: data.title as string,\n page_count: (data.page_count as number) ?? 0,\n status: data.status as \"pending_upload\" | \"pending\" | \"processing\" | \"ready\" | \"failed\",\n page_images: [],\n toc: [],\n download_url: null,\n }\n }\n\n // Browser-safe env lookup via globalThis. The client package builds without\n // @types/node so referring to `process` directly fails the dts build; reading\n // through globalThis sidesteps that and works in every JS environment that\n // matters (Node, browsers, edge runtimes).\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n const cdnBase = proc?.env?.NEXT_PUBLIC_R2_PUBLIC_URL ?? \"https://cdn.distinctstudio.co.nz\"\n const prefix = `flipbooks/${data.tenant_id as string}/${data.id as string}/`\n return {\n id: data.id as string,\n title: data.title as string,\n page_count: (data.page_count as number) ?? manifest.pages.length,\n status: data.status as \"pending\" | \"processing\" | \"ready\" | \"failed\",\n page_images: manifest.pages.map((p) => ({\n url: `${cdnBase}/${prefix}${p.image}`,\n thumb_url: `${cdnBase}/${prefix}${p.thumb}`,\n w: p.w,\n h: p.h,\n })),\n toc: manifest.toc,\n download_url: data.download_enabled\n ? `${options.appUrl ?? \"\"}/api/flipbooks/${data.id as string}/download`\n : null,\n }\n }\n\n return {\n /**\n * List content items for a content type.\n * Defaults to published items ordered by most recent.\n */\n async getContentItems(\n contentTypeSlug: string,\n options: ContentQueryOptions = {}\n ): Promise<(ContentItem & { locked?: boolean })[]> {\n const contentType = await getContentTypeBySlug(contentTypeSlug)\n\n const {\n status = \"published\",\n orderBy = \"published_at\",\n orderDirection = \"desc\",\n limit = 100,\n offset = 0,\n memberToken,\n } = options\n\n // Check membership access\n const access = await checkMemberAccess(contentType, memberToken)\n\n // If gated and no access, check gating mode\n if (!access.allowed && contentType.required_membership_tier_id) {\n // Get tenant settings for gating mode\n const tenantId = await getTenantId()\n const { data: settings } = await client\n .from(\"tenant_settings\")\n .select(\"membership_gating_mode\")\n .eq(\"tenant_id\", tenantId)\n .single()\n\n const mode = (settings?.membership_gating_mode as string) ?? \"teaser\"\n\n if (mode === \"hide\") {\n return [] // Hide: return nothing\n }\n\n // Teaser mode: return items with locked flag, no body/data\n let query = client\n .from(\"content_items\")\n .select(\"id, title, slug, status, excerpt, seo_title, seo_description, featured_image, published_at, created_at, updated_at\")\n .eq(\"content_type_id\", contentType.id)\n\n if (status) query = query.eq(\"status\", status)\n query = query\n .order(orderBy, { ascending: orderDirection === \"asc\" })\n .range(offset, offset + limit - 1)\n\n const { data, error } = await query\n if (error) throw new Error(`Failed to fetch ${contentTypeSlug}: ${error.message}`)\n\n return (data ?? []).map((item) => ({\n ...item,\n data: {},\n locked: true,\n })) as (ContentItem & { locked: boolean })[]\n }\n\n // Full access\n let query = client\n .from(\"content_items\")\n .select(\"*\")\n .eq(\"content_type_id\", contentType.id)\n\n if (status) {\n query = query.eq(\"status\", status)\n }\n\n query = query\n .order(orderBy, { ascending: orderDirection === \"asc\" })\n .range(offset, offset + limit - 1)\n\n const { data, error } = await query\n\n if (error) {\n throw new Error(`Failed to fetch ${contentTypeSlug}: ${error.message}`)\n }\n\n return (data ?? []) as ContentItem[]\n },\n\n /**\n * Get a single content item by its slug.\n * Returns null if not found.\n */\n async getContentItemBySlug(\n contentTypeSlug: string,\n itemSlug: string\n ): Promise<ContentItem | null> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"content_items\")\n .select(\"*\")\n .eq(\"content_type_id\", contentTypeId)\n .eq(\"slug\", itemSlug)\n .single()\n\n if (error) {\n if (error.code === \"PGRST116\") return null // not found\n throw new Error(\n `Failed to fetch ${contentTypeSlug}/${itemSlug}: ${error.message}`\n )\n }\n\n return data as ContentItem\n },\n\n /**\n * Get the single published item for a singleton content type (\"page\").\n *\n * A singleton content type owns exactly one item. Use this for editable\n * page copy (hero text, section headings, etc.) stored in the CMS.\n * Returns null when the singleton has no published item yet — callers\n * should fall back to their hardcoded defaults in that case.\n *\n * Wrap this in `unstable_cache` with a `cms:singleton:<slug>` tag on the\n * consuming site to cache it and revalidate via the CMS webhook.\n */\n async getSingleton(contentTypeSlug: string): Promise<ContentItem | null> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"content_items\")\n .select(\"*\")\n .eq(\"content_type_id\", contentTypeId)\n .eq(\"status\", \"published\")\n .order(\"updated_at\", { ascending: false })\n .limit(1)\n .maybeSingle()\n\n if (error) {\n throw new Error(\n `Failed to fetch singleton ${contentTypeSlug}: ${error.message}`\n )\n }\n\n return (data as ContentItem) ?? null\n },\n\n /**\n * Get a content type definition (including its field schema).\n */\n async getContentType(contentTypeSlug: string): Promise<ContentType> {\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"content_types\")\n .select(\"*\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", contentTypeSlug)\n .single()\n\n if (error || !data) {\n throw new Error(`Content type not found: ${contentTypeSlug}`)\n }\n\n return data as ContentType\n },\n\n /**\n * Get all slugs for a content type (for generateStaticParams).\n */\n async getAllSlugs(\n contentTypeSlug: string\n ): Promise<{ slug: string }[]> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"content_items\")\n .select(\"slug\")\n .eq(\"content_type_id\", contentTypeId)\n .eq(\"status\", \"published\")\n\n if (error) {\n throw new Error(\n `Failed to fetch slugs for ${contentTypeSlug}: ${error.message}`\n )\n }\n\n return (data ?? []) as { slug: string }[]\n },\n\n /**\n * List all content types for this tenant.\n */\n async getContentTypes(): Promise<ContentType[]> {\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"content_types\")\n .select(\"*\")\n .eq(\"tenant_id\", tenantId)\n .order(\"name\")\n\n if (error) {\n throw new Error(`Failed to fetch content types: ${error.message}`)\n }\n\n return (data ?? []) as ContentType[]\n },\n\n /**\n * Get all slug redirects for a content type.\n * Use in next.config.js redirects() or middleware for 301s.\n * Returns: [{ old_slug, new_slug }, ...]\n */\n async getRedirects(\n contentTypeSlug: string\n ): Promise<{ old_slug: string; new_slug: string }[]> {\n const contentTypeId = await getContentTypeId(contentTypeSlug)\n\n const { data, error } = await client\n .from(\"slug_redirects\")\n .select(\"old_slug, new_slug\")\n .eq(\"content_type_id\", contentTypeId)\n\n if (error) {\n throw new Error(\n `Failed to fetch redirects for ${contentTypeSlug}: ${error.message}`\n )\n }\n\n return (data ?? []) as { old_slug: string; new_slug: string }[]\n },\n\n /**\n * Get all custom path redirects for this tenant.\n * Use alongside getRedirects() in next.config.ts for full redirect coverage.\n */\n async getCustomRedirects(): Promise<\n { source_path: string; destination_path: string; permanent: boolean }[]\n > {\n const tenantId = await getTenantId()\n\n const { data, error } = await client\n .from(\"custom_redirects\")\n .select(\"source_path, destination_path, permanent\")\n .eq(\"tenant_id\", tenantId)\n\n if (error) {\n throw new Error(`Failed to fetch custom redirects: ${error.message}`)\n }\n\n return (data ?? []) as {\n source_path: string\n destination_path: string\n permanent: boolean\n }[]\n },\n\n /**\n * Get form submissions for a specific form.\n * Useful for displaying testimonials, reviews, etc.\n */\n async getFormSubmissions(\n formSlug: string,\n options: { status?: string; limit?: number; offset?: number } = {}\n ): Promise<Record<string, unknown>[]> {\n const tenantId = await getTenantId()\n\n const { data: form } = await client\n .from(\"forms\")\n .select(\"id\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", formSlug)\n .single()\n\n if (!form) return []\n\n const { limit = 50, offset = 0, status } = options\n\n let query = client\n .from(\"form_submissions\")\n .select(\"*\")\n .eq(\"form_id\", form.id)\n .eq(\"is_spam\", false)\n .order(\"created_at\", { ascending: false })\n .range(offset, offset + limit - 1)\n\n if (status) {\n query = query.eq(\"status\", status)\n }\n\n const { data } = await query\n return (data ?? []) as Record<string, unknown>[]\n },\n\n /**\n * Get a form configuration by slug.\n */\n async getForm(\n formSlug: string\n ): Promise<Record<string, unknown> | null> {\n const tenantId = await getTenantId()\n\n const { data } = await client\n .from(\"forms\")\n .select(\"id, name, slug, description, is_active\")\n .eq(\"tenant_id\", tenantId)\n .eq(\"slug\", formSlug)\n .single()\n\n return data as Record<string, unknown> | null\n },\n\n /**\n * Get a flipbook by ID, including CDN-resolved page image URLs.\n * Returns null if not found or manifest not yet ready.\n */\n getFlipbook,\n\n /**\n * Get Google Reviews for this tenant.\n * Defaults to approved reviews ordered by most recent.\n */\n async getReviews(\n options: ReviewQueryOptions = {}\n ): Promise<GoogleReview[]> {\n const tenantId = await getTenantId()\n\n const {\n status = \"approved\",\n minRating,\n limit = 50,\n offset = 0,\n orderBy = \"review_timestamp\",\n orderDirection = \"desc\",\n } = options\n\n let query = client\n .from(\"google_reviews\")\n .select(\"id, author_name, author_photo_url, rating, text, review_timestamp\")\n .eq(\"tenant_id\", tenantId)\n\n if (status) {\n query = query.eq(\"status\", status)\n }\n\n if (minRating) {\n query = query.gte(\"rating\", minRating)\n }\n\n query = query\n .order(orderBy, { ascending: orderDirection === \"asc\" })\n .range(offset, offset + limit - 1)\n\n const { data, error } = await query\n\n if (error) {\n throw new Error(`Failed to fetch reviews: ${error.message}`)\n }\n\n return (data ?? []) as GoogleReview[]\n },\n\n /**\n * Get the tenant's third-party tracking IDs (Google Analytics, Google Tag Manager, Meta Pixel).\n * Each value is null when not configured. These IDs are public and safe to render in HTML.\n *\n * On Next.js, the result is cached and revalidated every hour by default\n * (`revalidate: 3600`) and tagged with `TRACKING_CONFIG_TAG`. Tenant sites\n * that want zero-lag updates can call `revalidateTag(TRACKING_CONFIG_TAG)`\n * from a webhook. Pass `revalidate: 0` to disable caching, `revalidate: false`\n * to cache indefinitely (tag-only invalidation), or any positive integer\n * (seconds) to override the interval. Outside Next.js the cache hints are\n * silently ignored — every call hits the network.\n */\n async getTrackingConfig(options: TrackingConfigOptions = {}): Promise<TrackingConfig> {\n const { revalidate = 3600, tags = [TRACKING_CONFIG_TAG] } = options\n const supabaseUrl = (supabase as unknown as { supabaseUrl: string }).supabaseUrl\n const supabaseKey = (supabase as unknown as { supabaseKey: string }).supabaseKey\n\n // Direct PostgREST fetch (instead of going through supabase-js) so that\n // Next.js sees the request and applies its `next` cache options. RLS\n // restricts the result to the tenant matching `x-cms-api-key`, so no\n // explicit tenant_id filter is needed.\n const url =\n `${supabaseUrl}/rest/v1/tenant_settings` +\n `?select=google_analytics_id,google_tag_manager_id,google_ads_id,meta_pixel_id&limit=1`\n\n const init: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } } = {\n headers: {\n apikey: supabaseKey,\n Authorization: `Bearer ${supabaseKey}`,\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n Accept: \"application/json\",\n },\n next: { revalidate, tags },\n }\n\n let row: {\n google_analytics_id: string | null\n google_tag_manager_id: string | null\n google_ads_id: string | null\n meta_pixel_id: string | null\n } | undefined\n\n try {\n const res = await fetch(url, init as RequestInit)\n if (res.ok) {\n const rows = (await res.json()) as Array<typeof row>\n row = rows?.[0]\n }\n } catch {\n // Network errors fall through to the all-null result so a transient\n // outage on the CMS never breaks page renders on the tenant site.\n }\n\n return {\n googleAnalyticsId: nullify(row?.google_analytics_id),\n googleTagManagerId: nullify(row?.google_tag_manager_id),\n googleAdsId: nullify(row?.google_ads_id),\n metaPixelId: nullify(row?.meta_pixel_id),\n }\n },\n\n /**\n * Get the tenant's Mapbox **public** token (`pk.*`) for rendering maps in the\n * browser. Returns `{ publicToken: null }` when not configured. The Mapbox\n * **secret** token is never exposed by the CMS — read it from your own\n * environment variables.\n *\n * Caching mirrors `getTrackingConfig()`: revalidated hourly by default and\n * tagged with `MAPBOX_CONFIG_TAG`. Call `revalidateTag(MAPBOX_CONFIG_TAG)`\n * from a `settings.mapbox_updated` webhook for zero-lag updates.\n */\n async getMapboxConfig(options: MapboxConfigOptions = {}): Promise<MapboxConfig> {\n const { revalidate = 3600, tags = [MAPBOX_CONFIG_TAG] } = options\n const supabaseUrl = (supabase as unknown as { supabaseUrl: string }).supabaseUrl\n const supabaseKey = (supabase as unknown as { supabaseKey: string }).supabaseKey\n\n const url =\n `${supabaseUrl}/rest/v1/tenant_settings` +\n `?select=mapbox_public_token&limit=1`\n\n const init: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } } = {\n headers: {\n apikey: supabaseKey,\n Authorization: `Bearer ${supabaseKey}`,\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n Accept: \"application/json\",\n },\n next: { revalidate, tags },\n }\n\n let row: { mapbox_public_token: string | null } | undefined\n\n try {\n const res = await fetch(url, init as RequestInit)\n if (res.ok) {\n const rows = (await res.json()) as Array<typeof row>\n row = rows?.[0]\n }\n } catch {\n // Network errors fall through to the null result so a transient CMS\n // outage never breaks map rendering on the tenant site.\n }\n\n return { publicToken: nullify(row?.mapbox_public_token) }\n },\n }\n}\n\n/**\n * Cache tag applied to `getTrackingConfig()` fetches on Next.js. Call\n * `revalidateTag(TRACKING_CONFIG_TAG)` from a webhook handler on the tenant\n * site to make tracking-ID changes take effect immediately rather than\n * waiting for the next revalidation interval.\n */\nexport const TRACKING_CONFIG_TAG = \"cms:tracking-config\"\n\nexport interface TrackingConfigOptions {\n /**\n * Cache lifetime for the underlying fetch on Next.js, in seconds.\n * Defaults to 3600 (one hour). Set to `0` to disable caching, or `false`\n * to cache indefinitely until the tag is revalidated. Ignored outside\n * Next.js runtimes.\n */\n revalidate?: number | false\n /**\n * Cache tags for the underlying fetch on Next.js. Defaults to\n * `[TRACKING_CONFIG_TAG]`. Override to namespace by tenant if you share a\n * single Next.js process across multiple tenants (uncommon).\n */\n tags?: string[]\n}\n\n/**\n * Cache tag applied to `getMapboxConfig()` fetches on Next.js. Call\n * `revalidateTag(MAPBOX_CONFIG_TAG)` from a `settings.mapbox_updated` webhook\n * handler so public-token changes take effect immediately.\n */\nexport const MAPBOX_CONFIG_TAG = \"cms:mapbox-config\"\n\n/**\n * Cache tag for a singleton \"page\" content type. Wrap `getSingleton(slug)` in\n * `unstable_cache` tagged with `singletonTag(slug)`, then call\n * `revalidateTag(singletonTag(slug))` from the CMS `content.updated` webhook\n * (the webhook payload's `cache_tags` already include this value).\n */\nexport function singletonTag(slug: string): string {\n return `cms:singleton:${slug}`\n}\n\nexport interface MapboxConfigOptions {\n /** Cache lifetime in seconds on Next.js. Defaults to 3600. `0` disables caching, `false` caches until tag revalidation. Ignored outside Next.js. */\n revalidate?: number | false\n /** Cache tags applied to the fetch. Defaults to `[MAPBOX_CONFIG_TAG]`. */\n tags?: string[]\n}\n\nexport interface MapboxConfig {\n /** The tenant's Mapbox public token (`pk.*`), or null when not configured. */\n publicToken: string | null\n}\n\nfunction nullify(v: string | null | undefined): string | null {\n if (!v) return null\n const trimmed = v.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport interface TrackingConfig {\n googleAnalyticsId: string | null\n googleTagManagerId: string | null\n googleAdsId: string | null\n metaPixelId: string | null\n}\n\n/**\n * Creates a new Supabase client that includes the `x-cms-api-key`\n * header in every request, using the same URL and key as the original.\n */\nfunction withApiKey(supabase: SupabaseClient, apiKey: string) {\n // Extract URL and key from the existing client\n const supabaseUrl = (supabase as unknown as { supabaseUrl: string }).supabaseUrl\n const supabaseKey = (supabase as unknown as { supabaseKey: string }).supabaseKey\n\n return createSupabaseClient(supabaseUrl, supabaseKey, {\n global: {\n headers: {\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n },\n },\n })\n}\n","import { CMS_CLIENT_VERSION } from \"./version\"\n\n/**\n * Reports the running SDK version to the CMS so the Super Admin UI can show\n * which client version each tenant site is on.\n *\n * Day-granular and process-local: at most one POST per (api key + day) per\n * Node process. Calls from browser code are ignored — the API key would be\n * exposed and the data is redundant with server-side reports.\n *\n * Fire-and-forget. Failures are silent — telemetry must never break a render.\n */\n\nconst DEFAULT_APP_URL = \"https://cms.distinctstudio.co.nz\"\n\ninterface ReportRecord {\n date: string // YYYY-MM-DD\n version: string\n}\n\nconst reported = new Map<string, ReportRecord>()\n\nfunction todayKey(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nexport function reportClientVersion(apiKey: string, appUrl?: string): void {\n // Skip in browser contexts — server-side calls already cover the tenant.\n if (typeof window !== \"undefined\") return\n if (!apiKey) return\n\n const today = todayKey()\n const last = reported.get(apiKey)\n if (last && last.date === today && last.version === CMS_CLIENT_VERSION) return\n\n // Optimistically mark as reported so concurrent calls don't all fire. If the\n // request fails we'll retry tomorrow — that's acceptable for telemetry.\n reported.set(apiKey, { date: today, version: CMS_CLIENT_VERSION })\n\n const envAppUrl =\n typeof process !== \"undefined\" ? process.env?.NEXT_PUBLIC_CMS_URL : undefined\n const base = (appUrl ?? envAppUrl ?? DEFAULT_APP_URL).replace(/\\/$/, \"\")\n const url = `${base}/api/client-telemetry`\n\n // Fire and forget. Use a hand-rolled then() chain rather than async/await so\n // we don't accidentally surface an unhandled rejection.\n void fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n },\n body: JSON.stringify({\n api_key: apiKey,\n version: CMS_CLIENT_VERSION,\n }),\n }).catch(() => {\n // Allow a retry on the next call by clearing the optimistic record.\n reported.delete(apiKey)\n })\n}\n","import type { EmbedValue } from \"./types\"\n\nfunction toCssAspectRatio(ratio: string): string {\n const parts = ratio.split(\":\")\n if (parts.length !== 2) return \"16/9\"\n const w = parseFloat(parts[0])\n const h = parseFloat(parts[1])\n if (!w || !h || w <= 0 || h <= 0) return \"16/9\"\n return `${w}/${h}`\n}\n\n/**\n * Generate responsive iframe HTML for an embed field value.\n * Returns an empty string if the value is invalid or the URL is not HTTPS.\n */\nexport function getEmbedHtml(value: unknown): string {\n if (!value || typeof value !== \"object\") return \"\"\n const embed = value as EmbedValue\n if (!embed.url || !embed.url.startsWith(\"https://\")) return \"\"\n\n const width = embed.width || \"100%\"\n const aspectRatio = toCssAspectRatio(embed.aspect_ratio || \"16:9\")\n\n return `<div style=\"position:relative;width:${width};aspect-ratio:${aspectRatio}\"><iframe src=\"${embed.url}\" style=\"position:absolute;inset:0;width:100%;height:100%\" frameborder=\"0\" sandbox=\"allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox\" allowfullscreen loading=\"lazy\"></iframe></div>`\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\"\nimport type {\n Product,\n ProductCategory,\n ProductQueryOptions,\n CreateOrderParams,\n CreateOrderResult,\n} from \"./types\"\nimport { CMS_CLIENT_VERSION } from \"./version\"\nimport { reportClientVersion } from \"./telemetry\"\n\nconst DEFAULT_APP_URL = \"https://cms.distinctstudio.co.nz\"\n\nexport interface ShopClientOptions {\n /** Tenant API key (UUID). Sent as `x-cms-api-key` on every request. */\n apiKey: string\n /** CMS app base URL. Defaults to `https://cms.distinctstudio.co.nz`. */\n appUrl?: string\n}\n\n/**\n * Creates a typed client for the eCommerce REST API (products, categories, orders).\n *\n * Unlike `createCmsClient`, the shop client talks to the CMS REST endpoints\n * (`/api/products`, `/api/product-categories`, `/api/orders/create`) rather than\n * Supabase directly — it doesn't need the `supabase` argument, but accepts it\n * for API symmetry with the rest of the SDK.\n *\n * Usage:\n * ```ts\n * const shop = createShopClient(supabase, { apiKey: process.env.CMS_API_KEY! })\n * const products = await shop.getProducts({ category: \"electronics\", limit: 20 })\n * ```\n */\nexport function createShopClient(\n _supabase: SupabaseClient,\n options: ShopClientOptions\n) {\n const { apiKey } = options\n const appUrl = (options.appUrl ?? DEFAULT_APP_URL).replace(/\\/$/, \"\")\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, appUrl)\n\n function authHeaders(): HeadersInit {\n return {\n \"Content-Type\": \"application/json\",\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n }\n }\n\n async function getJson<T>(path: string): Promise<T> {\n const res = await fetch(`${appUrl}${path}`, {\n headers: authHeaders(),\n // Caller controls Next.js caching by wrapping the call site.\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}) as { error?: string })\n throw new Error(\n body.error ?? `Shop request failed (${res.status}): ${path}`\n )\n }\n return res.json() as Promise<T>\n }\n\n return {\n /**\n * List published products. Includes nested `variants` and `options`.\n *\n * `category` filters by category slug (matches the FK `category_id`,\n * with a fallback to the legacy `category` text column).\n */\n async getProducts(queryOptions: ProductQueryOptions = {}): Promise<Product[]> {\n const params = new URLSearchParams()\n if (queryOptions.category) params.set(\"category\", queryOptions.category)\n if (queryOptions.tags?.length) params.set(\"tags\", queryOptions.tags.join(\",\"))\n if (queryOptions.limit) params.set(\"limit\", String(queryOptions.limit))\n if (queryOptions.offset) params.set(\"offset\", String(queryOptions.offset))\n if (queryOptions.sort) params.set(\"sort\", queryOptions.sort)\n if (queryOptions.order) params.set(\"order\", queryOptions.order)\n\n const qs = params.toString()\n const { products } = await getJson<{ products: Product[] }>(\n `/api/products${qs ? `?${qs}` : \"\"}`\n )\n return products ?? []\n },\n\n /**\n * Get a single published product by slug. Returns null if not found.\n * Includes nested `variants` and `options`.\n */\n async getProductBySlug(slug: string): Promise<Product | null> {\n const res = await fetch(`${appUrl}/api/products/${encodeURIComponent(slug)}`, {\n headers: authHeaders(),\n })\n if (res.status === 404) return null\n if (!res.ok) {\n const body = await res.json().catch(() => ({}) as { error?: string })\n throw new Error(body.error ?? `Failed to fetch product ${slug}`)\n }\n const { product } = (await res.json()) as { product: Product }\n return product ?? null\n },\n\n /**\n * List product categories for the tenant, ordered by sort_order then name.\n */\n async getCategories(): Promise<ProductCategory[]> {\n const { categories } = await getJson<{ categories: ProductCategory[] }>(\n `/api/product-categories`\n )\n return categories ?? []\n },\n\n /**\n * Get a single category by slug. Returns null if not found.\n */\n async getCategoryBySlug(slug: string): Promise<ProductCategory | null> {\n const all = await this.getCategories()\n return all.find((c) => c.slug === slug) ?? null\n },\n\n /**\n * Create a pending order and return a Stripe PaymentIntent client_secret\n * to confirm payment client-side.\n *\n * Stripe must be configured for the tenant; eCommerce must be enabled on\n * their billing plan. Validates stock for tracked variants and applies\n * any discount code before creating the PaymentIntent.\n */\n async createOrder(params: CreateOrderParams): Promise<CreateOrderResult> {\n const res = await fetch(`${appUrl}/api/orders/create`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ api_key: apiKey, ...params }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}) as { error?: string })\n throw new Error(body.error ?? \"Order creation failed\")\n }\n return res.json() as Promise<CreateOrderResult>\n },\n }\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\"\nimport { reportClientVersion } from \"./telemetry\"\n\nexport type EventStatus = \"draft\" | \"published\" | \"archived\"\nexport type BookingStatus = \"pending\" | \"confirmed\" | \"cancelled\" | \"refunded\"\n\nexport interface CmsEvent {\n id: string\n tenant_id: string\n title: string\n slug: string\n description: string | null\n short_description: string | null\n status: EventStatus\n start_at: string\n end_at: string | null\n venue_name: string | null\n venue_address: string | null\n hero_image: string | null\n gallery: string[]\n tags: string[]\n seo_title: string | null\n seo_description: string | null\n metadata: Record<string, unknown>\n sort_order: number\n published_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface CmsTicketTier {\n id: string\n event_id: string\n tenant_id: string\n name: string\n description: string | null\n price_cents: number\n capacity: number | null\n sold_count: number\n sales_start_at: string | null\n sales_end_at: string | null\n is_active: boolean\n sort_order: number\n}\n\nexport interface EventWithTiers extends CmsEvent {\n tiers: CmsTicketTier[]\n}\n\nexport interface EventQueryOptions {\n /** Only include events with start_at >= now. Default false. */\n upcomingOnly?: boolean\n tag?: string\n limit?: number\n offset?: number\n sort?: \"start_at\" | \"sort_order\" | \"created_at\"\n order?: \"asc\" | \"desc\"\n}\n\nexport interface CreateBookingParams {\n event_slug: string\n ticket_tier_id?: string\n customer_email: string\n customer_name?: string\n customer_phone?: string\n quantity?: number\n success_url?: string\n cancel_url?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CreateBookingResult {\n booking_id: string\n booking_number: string\n status: \"pending\" | \"confirmed\"\n /** Redirect the browser here for paid bookings. */\n checkout_url?: string\n /** Present on free bookings — used for attendance QR display. */\n qr_token?: string\n}\n\nexport function createEventsClient(\n supabase: SupabaseClient,\n options: { apiKey: string; appUrl?: string }\n) {\n const { apiKey, appUrl } = options\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, appUrl)\n\n async function getEvents(\n queryOptions?: EventQueryOptions\n ): Promise<EventWithTiers[]> {\n let query = supabase\n .from(\"events\")\n .select(\"*, tiers:ticket_tiers(*)\")\n .eq(\"status\", \"published\")\n .order(queryOptions?.sort ?? \"start_at\", {\n ascending: (queryOptions?.order ?? \"asc\") === \"asc\",\n })\n\n if (queryOptions?.upcomingOnly) {\n query = query.gte(\"start_at\", new Date().toISOString())\n }\n if (queryOptions?.tag) {\n query = query.contains(\"tags\", [queryOptions.tag])\n }\n if (queryOptions?.limit) {\n query = query.limit(queryOptions.limit)\n }\n if (queryOptions?.offset) {\n query = query.range(\n queryOptions.offset,\n queryOptions.offset + (queryOptions.limit ?? 50) - 1\n )\n }\n\n const { data } = await query\n return (data ?? []) as EventWithTiers[]\n }\n\n async function getEventBySlug(slug: string): Promise<EventWithTiers | null> {\n const { data } = await supabase\n .from(\"events\")\n .select(\"*, tiers:ticket_tiers(*)\")\n .eq(\"slug\", slug)\n .eq(\"status\", \"published\")\n .single()\n return (data as EventWithTiers) ?? null\n }\n\n async function createBooking(\n params: CreateBookingParams\n ): Promise<CreateBookingResult> {\n const baseUrl = appUrl ?? \"\"\n const res = await fetch(`${baseUrl}/api/bookings/create`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ api_key: apiKey, ...params }),\n })\n if (!res.ok) {\n const err = await res\n .json()\n .catch(() => ({ error: \"Booking creation failed\" }))\n throw new Error(err.error ?? \"Booking creation failed\")\n }\n return res.json() as Promise<CreateBookingResult>\n }\n\n return { getEvents, getEventBySlug, createBooking }\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\"\nimport type { GeocodeOptions, GeocodeResult } from \"./types\"\nimport { CMS_CLIENT_VERSION } from \"./version\"\nimport { reportClientVersion } from \"./telemetry\"\n\nconst DEFAULT_APP_URL = \"https://cms.distinctstudio.co.nz\"\n\nexport interface MapsClientOptions {\n /** Tenant API key (UUID). Sent as `x-cms-api-key` on every request. */\n apiKey: string\n /** CMS app base URL. Defaults to `https://cms.distinctstudio.co.nz`. */\n appUrl?: string\n}\n\n/**\n * Creates a typed client for Mapbox-backed helpers (forward geocoding, with\n * more to come). Like `createShopClient`, it talks to CMS REST endpoints rather\n * than Supabase directly — it doesn't need the `supabase` argument but accepts\n * it for API symmetry. The Mapbox call runs server-side with the tenant's\n * public token; the token is never exposed to the browser.\n *\n * ```ts\n * const maps = createMapsClient(supabase, { apiKey: process.env.CMS_API_KEY! })\n * const [best] = await maps.geocodeAddress(\"12 Queen St, Auckland\")\n * ```\n */\nexport function createMapsClient(_supabase: SupabaseClient, options: MapsClientOptions) {\n const { apiKey } = options\n const appUrl = (options.appUrl ?? DEFAULT_APP_URL).replace(/\\/$/, \"\")\n\n // Day-granular, fire-and-forget version ping (server-side only).\n reportClientVersion(apiKey, appUrl)\n\n return {\n /**\n * Forward-geocode an address string. Returns matches ordered by relevance\n * (`results[0]` is the best match), or [] when nothing matches. Throws on a\n * configuration or upstream error.\n */\n async geocodeAddress(query: string, options: GeocodeOptions = {}): Promise<GeocodeResult[]> {\n const q = query.trim()\n if (!q) throw new Error(\"geocodeAddress: query is required\")\n\n const params = new URLSearchParams({ q })\n if (options.limit != null) params.set(\"limit\", String(options.limit))\n if (options.country) params.set(\"country\", options.country)\n if (options.proximity) params.set(\"proximity\", `${options.proximity[0]},${options.proximity[1]}`)\n if (options.types) params.set(\"types\", options.types)\n if (options.language) params.set(\"language\", options.language)\n\n const res = await fetch(`${appUrl}/api/mapbox/geocode?${params.toString()}`, {\n headers: {\n \"x-cms-api-key\": apiKey,\n \"x-cms-client-version\": CMS_CLIENT_VERSION,\n },\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string }\n throw new Error(body.error ?? `Geocoding failed (${res.status})`)\n }\n const data = (await res.json()) as { results?: GeocodeResult[] }\n return data.results ?? []\n },\n }\n}\n","/**\n * Image helpers for CMS content.\n *\n * Images are already optimised (WebP, max 2400px) on upload.\n * No server-side transforms needed — serve originals directly.\n *\n * These functions are kept for backward compatibility but no longer\n * call Supabase image transformation endpoints.\n */\n\nexport interface ImageTransformOptions {\n width?: number\n height?: number\n quality?: number\n resize?: \"contain\" | \"cover\" | \"fill\"\n format?: \"origin\"\n}\n\n/**\n * Returns the image URL directly.\n *\n * Previously this converted URLs to use Supabase's /render/image/\n * transform endpoint, but images are now pre-optimised on upload\n * (WebP, max 2400px, quality 82) so transforms are unnecessary.\n *\n * The function is kept for backward compatibility — existing code\n * that calls getTransformUrl() will continue to work without changes.\n */\nexport function getTransformUrl(\n originalUrl: string,\n _options: ImageTransformOptions = {}\n): string {\n return originalUrl\n}\n\n/**\n * Returns a simple srcSet using the original image.\n *\n * Previously generated multiple transformed widths, but since images\n * are now pre-optimised WebP, a single source is sufficient.\n * Modern browsers handle responsive display efficiently with a\n * well-sized WebP source.\n */\nexport function getSrcSet(\n originalUrl: string,\n _widths: number[] = [],\n _quality = 80\n): string {\n return `${originalUrl} 2400w`\n}\n\n/**\n * Common image size presets — kept for backward compatibility.\n * Since images are pre-optimised, these are informational only.\n */\nexport const IMAGE_PRESETS = {\n thumbnail: { width: 150, height: 150, resize: \"cover\" as const, quality: 70 },\n card: { width: 400, height: 300, resize: \"cover\" as const, quality: 80 },\n hero: { width: 1200, height: 630, resize: \"cover\" as const, quality: 85 },\n og: { width: 1200, height: 630, resize: \"cover\" as const, quality: 90 },\n avatar: { width: 80, height: 80, resize: \"cover\" as const, quality: 75 },\n full: { width: 1920, resize: \"contain\" as const, quality: 85 },\n} as const\n","import type { DateGranularity } from \"./types\"\n\nexport interface ParsedPartialDate {\n granularity: DateGranularity\n /** 4-digit year — absent for `month` and `day_month`. */\n year?: number\n /** 1-12 — absent for `year`. */\n month?: number\n /** 1-31 — present only for `full` and `day_month`. */\n day?: number\n}\n\n/**\n * Parses a partial-ISO date string (as produced by a `date` field) into its parts.\n * Returns `null` if the string doesn't match a known granularity shape.\n */\nexport function parsePartialDate(value: string): ParsedPartialDate | null {\n if (!value) return null\n let m: RegExpExecArray | null\n if ((m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value)))\n return { granularity: \"full\", year: +m[1], month: +m[2], day: +m[3] }\n if ((m = /^(\\d{4})-(\\d{2})$/.exec(value)))\n return { granularity: \"month_year\", year: +m[1], month: +m[2] }\n if ((m = /^(\\d{4})$/.exec(value))) return { granularity: \"year\", year: +m[1] }\n if ((m = /^--(\\d{2})-(\\d{2})$/.exec(value)))\n return { granularity: \"day_month\", month: +m[1], day: +m[2] }\n if ((m = /^--(\\d{2})$/.exec(value))) return { granularity: \"month\", month: +m[1] }\n return null\n}\n\nexport interface FormatPartialDateOptions {\n /** BCP 47 locale(s) passed to Intl.DateTimeFormat. Defaults to the runtime locale. */\n locale?: string | string[]\n /** Month rendering style. Defaults to `long` (e.g. \"May\"). */\n month?: \"long\" | \"short\" | \"narrow\" | \"numeric\" | \"2-digit\"\n}\n\n/**\n * Formats a partial-ISO date string for display, honouring the value's granularity\n * and locale-correct part ordering (e.g. \"12 May\" vs \"May 12\"). Returns the raw\n * string unchanged if it isn't a recognised partial date.\n */\nexport function formatPartialDate(value: string, options: FormatPartialDateOptions = {}): string {\n const parsed = parsePartialDate(value)\n if (!parsed) return value\n\n const { locale, month = \"long\" } = options\n const monthIndex = parsed.month ? parsed.month - 1 : 0\n\n switch (parsed.granularity) {\n case \"year\":\n return String(parsed.year)\n case \"month\":\n return new Intl.DateTimeFormat(locale, { month }).format(new Date(2000, monthIndex, 1))\n case \"month_year\":\n return new Intl.DateTimeFormat(locale, { month, year: \"numeric\" }).format(\n new Date(parsed.year!, monthIndex, 1)\n )\n case \"day_month\":\n return new Intl.DateTimeFormat(locale, { month, day: \"numeric\" }).format(\n new Date(2000, monthIndex, parsed.day!)\n )\n case \"full\":\n return new Intl.DateTimeFormat(locale, { year: \"numeric\", month, day: \"numeric\" }).format(\n new Date(parsed.year!, monthIndex, parsed.day!)\n )\n }\n}\n","/**\n * Helpers for verifying outbound webhooks fired by the CMS.\n *\n * The CMS POSTs JSON to subscriber URLs and signs the raw body with\n * HMAC-SHA256 using the shared secret you set in the Webhooks tab. The hex\n * digest arrives in the `X-CMS-Signature` header.\n *\n * Use Web Crypto so this works in Node, Edge, Deno, and Bun runtimes.\n */\n\nexport interface WebhookEventPayload {\n event: string\n tenant_id: string\n content_type_slug?: string\n content_item_id?: string\n slug?: string\n title?: string\n status?: string\n /** Stable identifier of the resource that changed (e.g. product id, flipbook id, integration provider). */\n resource_id?: string\n /**\n * Cache tags the receiver should invalidate. Use {@link revalidateAllTags}\n * to wire this through Next.js's `revalidateTag()` in one line.\n * All tags are namespaced with the `cms:` prefix.\n */\n cache_tags?: string[]\n /** Free-form bag for event-specific extras (e.g. provider name, change counts). */\n data?: Record<string, unknown>\n timestamp: string\n /** Commerce + custom events may attach extra top-level fields. */\n [key: string]: unknown\n}\n\n/**\n * Verify the HMAC-SHA256 signature on a webhook delivery.\n *\n * Pass the **raw** request body (not a parsed JSON object) — re-stringifying\n * a parsed body can change whitespace and invalidate the signature.\n *\n * Returns `true` when the signature matches in constant time, `false`\n * otherwise. Never throws on a bad signature; only throws if the\n * `crypto.subtle` API is unavailable in the runtime.\n *\n * @example\n * const raw = await req.text()\n * const sig = req.headers.get(\"x-cms-signature\")\n * if (!await verifyWebhookSignature(process.env.CMS_WEBHOOK_SECRET!, raw, sig)) {\n * return new Response(\"bad signature\", { status: 401 })\n * }\n * const payload = JSON.parse(raw) as WebhookEventPayload\n */\nexport async function verifyWebhookSignature(\n secret: string,\n rawBody: string,\n signature: string | null | undefined\n): Promise<boolean> {\n if (!signature || !secret) return false\n\n const subtle = (globalThis.crypto && globalThis.crypto.subtle) || null\n if (!subtle) {\n throw new Error(\n \"verifyWebhookSignature requires the Web Crypto API (globalThis.crypto.subtle). \" +\n \"Available in Node 18+, all Edge runtimes, Deno, and Bun.\"\n )\n }\n\n const encoder = new TextEncoder()\n const key = await subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n )\n const sigBuffer = await subtle.sign(\"HMAC\", key, encoder.encode(rawBody))\n const expected = bufferToHex(sigBuffer)\n\n return timingSafeEqualHex(expected, signature.trim())\n}\n\n/**\n * The webhook event names the CMS can fire. Use as a discriminator on the\n * `event` field of {@link WebhookEventPayload}.\n *\n * Most receivers don't need to switch on the event name — every payload\n * carries a `cache_tags` array. Pass it to {@link revalidateAllTags} to\n * invalidate the right Next.js caches in one line.\n */\nexport const WEBHOOK_EVENTS = [\n // Content\n \"content.published\",\n \"content.unpublished\",\n \"content.updated\",\n \"content.deleted\",\n \"content_type.updated\",\n \"content_type.deleted\",\n // Settings (diffed — only fires when a relevant field actually changed)\n \"settings.tracking_updated\",\n \"settings.brand_updated\",\n \"settings.integration_updated\",\n \"settings.mapbox_updated\",\n // Commerce\n \"products.updated\",\n \"product_categories.updated\",\n \"ticket_tiers.updated\",\n \"membership_tiers.updated\",\n \"order.created\",\n \"order.paid\",\n \"order.payment_failed\",\n \"order.refunded\",\n \"order.shipped\",\n \"booking.confirmed\",\n \"inventory.low_stock\",\n // Site\n \"redirects.updated\",\n \"reviews.synced\",\n \"flipbook.ready\",\n] as const\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number]\n\n/**\n * Invalidate every cache tag listed in a webhook payload. Call this from your\n * receiver and most events will Just Work without per-event branching.\n *\n * Pass Next.js's `revalidateTag` (or any function with the same shape) — the\n * SDK has no peer dep on `next/cache`, so the import stays in your code.\n *\n * **Next 16 compatibility.** In Next 16, `revalidateTag(tag)` was made into\n * `revalidateTag(tag, profile)`, and calling it with one argument emits a\n * deprecation warning. The cache profile controls expiry — `\"default\"` and\n * `\"max\"` both have non-zero `expire` values and will **not** invalidate the\n * cache. Immediate invalidation requires `{ expire: 0 }`, which isn't\n * documented in the function reference. This helper always passes\n * `{ expire: 0 }` as the second argument, so it does the right thing on Next 16\n * and is silently ignored by Next 14/15.\n *\n * @example\n * import { revalidateTag } from \"next/cache\"\n * import { revalidateAllTags } from \"@distinctagency/cms-client\"\n *\n * export async function POST(req: Request) {\n * const raw = await req.text()\n * if (!await verifyWebhookSignature(SECRET, raw, req.headers.get(\"x-cms-signature\"))) {\n * return new Response(\"bad sig\", { status: 401 })\n * }\n * const payload = JSON.parse(raw) as WebhookEventPayload\n * revalidateAllTags(payload, revalidateTag)\n * return Response.json({ ok: true })\n * }\n */\nexport function revalidateAllTags(\n payload: Pick<WebhookEventPayload, \"cache_tags\">,\n revalidateTag: (tag: string, profile: { expire?: number }) => unknown\n): string[] {\n const tags = payload.cache_tags\n if (!Array.isArray(tags) || tags.length === 0) return []\n const invalidated: string[] = []\n for (const tag of tags) {\n if (typeof tag !== \"string\" || tag.length === 0) continue\n revalidateTag(tag, { expire: 0 })\n invalidated.push(tag)\n }\n return invalidated\n}\n\nfunction bufferToHex(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf)\n let out = \"\"\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i].toString(16).padStart(2, \"0\")\n }\n return out\n}\n\n/**\n * Constant-time equality check on two hex strings. Returns false fast when\n * lengths differ — the length itself is not secret.\n */\nfunction timingSafeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let mismatch = 0\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return mismatch === 0\n}\n","/** Discriminator stamped on every visual-editing postMessage. */\nexport const CMS_VISUAL_SOURCE = \"cms-visual\" as const\n\n/** CMS origins the bridge will trust by default. */\nexport const DEFAULT_TRUSTED_ORIGINS: readonly string[] = [\n \"https://cms.distinctstudio.co.nz\",\n \"https://distinctcms.com\",\n]\n\nexport type VisualFieldType = \"text\" | \"image\"\n\n/** Compact wire type announced in `ready`; the schema adds length constraints. */\nexport interface VisualFieldMeta {\n name: string\n type: VisualFieldType\n}\n\nexport interface VisualFieldSchema {\n name: string\n type: VisualFieldType\n max_length?: number\n recommended_length?: number\n}\n\n// bridge -> parent\nexport interface ReadyMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"ready\"\n fields: VisualFieldMeta[]\n}\nexport interface EditMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"edit\"\n name: string\n value: string\n length: number\n /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\n}\nexport interface PickImageMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"pick-image\"\n name: string\n /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\n}\n\n// parent -> bridge\nexport interface InitMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"init\"\n values: Record<string, string>\n schema: Record<string, VisualFieldSchema>\n}\nexport interface SetMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"set\"\n name: string\n value: string\n /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\n}\n\nexport type BridgeOutbound = ReadyMessage | EditMessage | PickImageMessage\nexport type BridgeInbound = InitMessage | SetMessage\nexport type VisualMessage = BridgeOutbound | BridgeInbound\n\n/** Exact-match origin allowlist, plus any localhost port for local dev. */\nexport function isTrustedOrigin(\n origin: string,\n allowlist: readonly string[]\n): boolean {\n if (allowlist.includes(origin)) return true\n return /^https?:\\/\\/localhost(:\\d+)?$/.test(origin)\n}\n\nconst VALID_VISUAL_TYPES = new Set([\"ready\", \"edit\", \"pick-image\", \"init\", \"set\"])\n\n/** Narrow an unknown postMessage payload to a visual message, or null. */\nexport function parseVisualMessage(data: unknown): VisualMessage | null {\n if (\n typeof data === \"object\" &&\n data !== null &&\n (data as Record<string, unknown>).source === CMS_VISUAL_SOURCE &&\n typeof (data as Record<string, unknown>).type === \"string\" &&\n VALID_VISUAL_TYPES.has((data as Record<string, unknown>).type as string)\n ) {\n return data as VisualMessage\n }\n return null\n}\n"],"mappings":"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","names":["options","query","data","error","createSupabaseClient","DEFAULT_APP_URL","DEFAULT_APP_URL","options"]}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var CMS_CLIENT_VERSION = "1.24.0";
2
+ var CMS_CLIENT_VERSION = "1.26.0";
3
3
 
4
4
  // src/queries.ts
5
5
  import { createClient as createSupabaseClient } from "@supabase/supabase-js";
@@ -755,8 +755,28 @@ function timingSafeEqualHex(a, b) {
755
755
  }
756
756
  return mismatch === 0;
757
757
  }
758
+
759
+ // src/visual-editing/protocol.ts
760
+ var CMS_VISUAL_SOURCE = "cms-visual";
761
+ var DEFAULT_TRUSTED_ORIGINS = [
762
+ "https://cms.distinctstudio.co.nz",
763
+ "https://distinctcms.com"
764
+ ];
765
+ function isTrustedOrigin(origin, allowlist) {
766
+ if (allowlist.includes(origin)) return true;
767
+ return /^https?:\/\/localhost(:\d+)?$/.test(origin);
768
+ }
769
+ var VALID_VISUAL_TYPES = /* @__PURE__ */ new Set(["ready", "edit", "pick-image", "init", "set"]);
770
+ function parseVisualMessage(data) {
771
+ if (typeof data === "object" && data !== null && data.source === CMS_VISUAL_SOURCE && typeof data.type === "string" && VALID_VISUAL_TYPES.has(data.type)) {
772
+ return data;
773
+ }
774
+ return null;
775
+ }
758
776
  export {
759
777
  CMS_CLIENT_VERSION,
778
+ CMS_VISUAL_SOURCE,
779
+ DEFAULT_TRUSTED_ORIGINS,
760
780
  IMAGE_PRESETS,
761
781
  MAPBOX_CONFIG_TAG,
762
782
  TRACKING_CONFIG_TAG,
@@ -769,7 +789,9 @@ export {
769
789
  getEmbedHtml,
770
790
  getSrcSet,
771
791
  getTransformUrl,
792
+ isTrustedOrigin,
772
793
  parsePartialDate,
794
+ parseVisualMessage,
773
795
  revalidateAllTags,
774
796
  singletonTag,
775
797
  verifyWebhookSignature