Configuration, environment, and session context.
System settings are stored encrypted in the database and accessed through a caching layer. Environment variables provide runtime configuration for the edge deployment.
The day counts a customisable listing offers, ascending: the priced counts
that fall within [1, duration_days] (duration_days is the maximum when
customisable_days is on). Empty for non-customisable listings.
Build a DayPrices map from raw entries. checkEntry reads one raw
key/value pair and returns the day count and price to keep, null to skip
that entry, or a problem message — which stops the walk and is returned in
place of the map. Shared by the lenient stored-value reader
(parseDayPrices) and the fail-closed admin API body parser.
Clamp a valid whole-day count to the supported booking range.
The per-ticket price (minor units) for booking days on a customisable
listing, or null when the listing isn't customisable or that count has no
configured price (and therefore isn't offered).
Get booking fee percentage from database. Returns 0 if not set.
Get the Botpoison public key from environment (safe to expose to browsers). Returns empty string when unset.
Get the Botpoison secret key from environment (server-side verification only). Returns empty string when unset.
Get the Bunny CDN API key from environment
Get the Bunny DNS subdomain suffix (e.g. ".tickets") from environment
Get the Bunny DNS zone ID from environment
Get the Bunny Edge Script ID from environment
Return the cached session if already resolved, or undefined if not yet resolved
Diagnostic key gating the verbose /health response. Empty when unset, in
which case /health only ever returns the plain liveness reply. Holding the
key reveals non-private build/runtime diagnostics (commit, build time) that
are useful for operators but needlessly helpful to an attacker.
Get the default database provider from DEFAULT_DB_HOST env var. Returns "turso" when set to "turso", "bunny" otherwise.
Get the Deno Deploy organization ID from environment.
Get the Deno Deploy organization slug used in managed production domains.
Get the Deno Deploy API token from environment.
Get the effective domain synchronously; DEFAULT_DOMAIN until a request resolves a real one.
Get allowed embed hosts from database (encrypted, parsed to array) Returns empty array if not configured (embedding allowed from anywhere)
Get an environment variable value Checks process.env first (Bunny Edge), falls back to Deno.env (local dev)
The shared secret authorizing the inter-instance site-credentials endpoint.
Get the READ_ONLY_FROM cutoff ISO string, or null if not set
Get the RENEWAL_URL, or null if not set
Get the Turso API token from environment.
Get the Turso database group from environment.
Get the Turso organization name from environment.
True when an attendee/booking row is a real ticket (quantity ≥ 1) rather than
the no-quantity sentinel (quantity 0). The shared "is this a real ticket, not a
ghost" test for the readers, rosters, and exports that must skip sentinel rows —
one home for the rule instead of a bare quantity > 0 plus an explanatory
comment at each call site.
Check if Botpoison spam protection is configured. Requires both BOTPOISON_PUBLIC_KEY and BOTPOISON_SECRET_KEY to be set. This gates the public contact form feature.
Check if this instance can build other sites.
Check if Bunny CDN pull zone management is enabled Requires both BUNNY_API_KEY and BUNNY_SCRIPT_ID to be set
Check if the Bunny hosted database provider is enabled (requires BUNNY_API_KEY).
Check if Bunny DNS subdomain feature is enabled. Requires BUNNY_API_KEY and BUNNY_DNS_ZONE_ID to be set.
Check if Deno Deploy hosting has its token, organization ID, and domain slug.
Whether the inter-instance site-credentials endpoint is enabled. Off unless MAIN_INSTANCE_KEY is set, so a non-builder instance never exposes it. The key is a high-entropy shared secret the operator passes to the upgrade workflow at trigger time (it is never stored in GitHub).
Pure helper: is the current time within the warning window before cutoff?
Whether an listing can accept payments: a flat price, pay-what-you-want, or a customisable-days listing with at least one non-zero day-count price.
Check if payments are enabled (any provider configured with valid keys)
Check if the system is in read-only mode based on the READ_ONLY_FROM cutoff
Pure helper: is the site read-only based on a cutoff timestamp?
Check if the site should show a pre-expiry warning banner
Type guard: a non-null, non-array object (a Record shape).
Whether we are serving a real, resolved host rather than the default. Gates
HTTPS-only behaviour — Secure/__Host- cookies and the HSTS header — which
must stay off for local development on DEFAULT_DOMAIN.
Check if Turso hosted database provider is enabled (requires TURSO_API_TOKEN, TURSO_ORGANIZATION, TURSO_GROUP).
Load the effective domain from DB, falling back to the request URL hostname.
Coerce an arbitrary stored/parsed value into a clean DayPrices map. Keeps only whole-number day counts in [1, MAX_DURATION_DAYS] mapped to finite, non-negative whole-number minor-unit prices; everything else is dropped. Used on both the DB read path and form parsing so the rest of the code can treat the map as already-valid.
Parse a flash cookie value into type, message, optional result, and the optional form re-fill stash token.
Parse a string into a positive integer for warning days. Returns defaultVal on bad input.
Pick the value for the active payment provider from a per-provider set, or
null when no known provider is active. The one place the stripe/square/sumup
dispatch lives, so every per-provider flag is read the same way.
Get a required environment variable, throwing if not set.
Use this instead of getEnv(key) as string when the variable must exist.
Reset effective domain cache back to the default (for testing).
Run a function within a session-memoization scope
Seed the effective domain from the request's own hostname.
Store the resolved session in the current request scope
Set effective domain directly (for testing).
Sanitize a site name into a valid provider resource slug: the shared
slugify, capped at maxLength with any hyphen the cut left trimmed.
The fields shared by every named, slugged content record whose free-text
columns are stored encrypted: the body/meta blobs, the display name, and the
/slug permalink paired with its plaintext HMAC blind index (slug_index).
Both site pages and news posts build on this.
A membership of listing_id in group_id, hydrated for the package editor
and booking flow. A listing may belong to several groups. package_price
(minor units) is the per-listing override when the group is a package, read
from the group dimension of listing_prices (not a column on the join
table): null means no override (use the listing's own price), 0 means
explicitly free in the package, and a positive value overrides the price.
quantity (≥1, stored on the join row) is how many of this listing one unit of
the package includes. Both are ignored for non-package groups.
A group paired with the active member listings used to decide and render it.
- active
- assign_built_site
- attachment_name
- attachment_url
- bookable_alone
- bookable_days
- can_pay_more
- closes_at
- created
- customisable_days
- date
- day_prices
- description
- duration_days
- fields
- hidden
- id
- initial_site_months
- listing_type
- location
- max_attendees
- max_price
- max_quantity
- maximum_days_after
- minimum_days_before
- months_per_unit
- name
- non_transferable
- purchase_only
- slug
- slug_index
- thank_you_url
- unit_price
- use_defaults
- uses_logistics
- webhook_url
An owner-defined price modifier (surcharge / discount / add-on). calc_value
is the positive magnitude the owner entered (a fixed amount in major currency
units, a percentage, or a multiplier); direction chooses charge vs discount.
A news post shown on the public /news page. All free-text columns are
stored encrypted; created stays plaintext (like listings) so the
newest-first ordering and the RSS pubDate never need a scan-and-decrypt.
slug is the /news/:slug permalink (auto-generated from the created date
and the name at creation, then immutable); slug_index is its blind index.
A user-created content page. Adds sort_order, which positions the page
among root-level pages.
One ordered membership edge: item (of item_type) sits inside page_id
at sort_order. Keyed on the composite (page_id, item_type, item_id).
A link between an agent user and a logistics agent (van/crew) they drive. Many-to-many: a user may cover several agents and an agent may have several users.
Admin role levels
Admin API listing shape — all listing fields except internal indices. Used by both admin JSON API and admin templates to ensure consistent field exposure. Snake_case keys match the DB schema.
A single row in the attendee table: an attendee plus the listings the row covers, in display order. Roster/check-in tables render one row per booking line (a one-listing array); the browsing tables (attendees list, dashboard) group an attendee's lines into one row carrying every listing.
Individual contact field name
Required name+email with optional phone/address/special_instructions from ContactInfo
Attendee contact details — the core PII fields collected at registration
The subset of listing fields needed to reason about day-count pricing.
Per-day-count ticket prices for "customisable days" listings, in minor
units, keyed by the number of days booked. e.g. { 1: 1000, 2: 1800 }
means a 1-day booking costs 1000 and a 2-day booking 1800. Only counts
present here are offered to the visitor.
The attendee fields the shared attendee table and its column registry
actually read. A full decrypted Attendee satisfies it, and so does a
field-selected read that skipped the money subqueries — which is exactly why
the browsing tables can render rows that never computed price_paid or
remaining_balance (see src/shared/db/attendees/select.ts).
A single part of an email template
Persisted email template type
Visual level of a flash message: a positive result, a failure, or a neutral acknowledgement.
The group ids each listing belongs to (listing id → group ids). A listing absent from the map belongs to no group.
Contact fields setting for an listing (comma-separated ContactField names, or empty for name-only). Alias kept for documentation; runtime enforcement happens in parseListingFields.
Listing type: standard (one-time) or daily (date-based booking)
Unique identifiers for settings nags that prompt the admin to complete required or recommended configuration.
The public /news list projection: a summary plus the post's first image (the shared ItemImageProjection columns).
The narrow list projection — id, created, slug, name, snippet — for readers
that render no images (the RSS feed, the admin list). Never the large
content/meta_* blobs (cold-start efficiency, like SitePageNavRow).
Supported payment provider identifiers
The kind of thing a SitePageItem points at. Exhaustive union — a new
member is a compile error at every Record<SitePageItemType, …> dispatch.
The listing values needed to place a listing in the shared sort order: which tier it belongs to (its type and date), its name for the within-tier sort, and — for a daily listing — the values that decide its next bookable date. A read that only lists or picks listings can select these columns alone and skip the whole listing record.
UI theme
Schema for admin role levels.
Every admin role level — used to gate actions every authenticated user must reach (e.g. logout). Derived from AdminLevelSchema so adding a new role propagates automatically instead of being hand-listed here.
All valid contact field names (runtime array matching the ContactField union)
Schema for an individual contact field name
Admin role levels that may create/edit listings & groups: the back-office
staff plus the content-only editor. Used to gate the listing/group content
routes editors are explicitly opted into; deliberately excludes agent.
Strict stored shape for per-day-count prices.
Admin role levels that may reach the delivery run sheet
(/admin/deliveries): staff plus delivery agents. This is the audience the
run sheet has always had; the content-only editor is excluded.
Schema for the parts of an email template: the subject line, the html body, and the plain-text body.
Schema for the persisted email template types: the attendee confirmation and the admin notification. The single source of truth for the template discriminator used by the renderer, settings store, and admin forms.
Schema for the kind of item an image can be attached to.
Type guard: check if a string is a valid AdminLevel
Type guard: check if an arbitrary string is a valid ContactField
True for roles that may create/edit listings & groups (owner/manager/editor).
True for roles that may reach the delivery run sheet (owner/manager/agent).
Type guard: check if a string is a valid EmailTemplateFormat
Type guard: check if a string is a valid EmailTemplateType
Type guard: check if an arbitrary string is a valid ListingType
True only for the owner role.
Type guard: check if a string is a valid PaymentProviderType
Type guard: check if a string is a valid PaymentProviderSetting
Type guard: is this string a valid SitePageItemType?
True for roles that may edit public-site content (owner/editor).
True for back-office staff (owner/manager).
Schema for a listing type: standard (one-time) or daily (date-based booking)
Upper bound on multi-day booking duration. Each day in a booking range adds a per-day clause to the atomic capacity SQL, so the cap keeps that statement bounded regardless of which write path set the value.
Units of a shared capped group consumed by one parent+child order: the parent line plus its single required child line each take one spot in the group they share (invariants I1, I7). Used to convert a shared group's remaining spots into how many whole parent+child orders still fit.
Schema for supported payment provider identifiers
Persisted payment-provider setting: an explicit provider, "none" (admin saved payments-disabled), or absent (never saved — drives the settings nag).
Admin role levels that may edit the public-facing site content (homepage,
contact, order intro). Site editing has always been owner-only; the editor
role is added to it, but manager stays excluded — so this is owner+editor,
NOT the broader CONTENT_ADMIN_LEVELS.
Schema for the kind of thing a SitePageItem points at.
Admin role levels that are back-office staff (not delivery agents).
Usage
import * as mod from "docs/config.ts";