Database client, ORM abstractions, and entity tables.

The database layer uses libsql with a type-safe table abstraction that handles column definitions, field transformers (encrypt/decrypt), and generic CRUD operations.

Entity Tables

  • Listings — listing CRUD with cached encrypted slugs/names
  • Attendees — hybrid RSA+AES encryption for PII
  • Users — password hashing, admin levels, wrapped keys
  • Sessions — token hashing with TTL caching
  • Groups — listing grouping with encrypted names
  • Settings — system configuration (currency, email, payment keys)
  • Holidays — date exclusions for daily listings
  • Activity Log — admin audit trail
  • Processed Payments — idempotency tracking
  • Login Attempts — rate limiting and lockout

Classes

c
DatabaseBusyError

Raised when a write can't get through because the database stays locked after the retries below — too busy. The request layer turns this into a friendly auto-reloading page rather than a generic error.

Functions

f
acceptInvite
No documentation available
f
activateKeylessUser

Complete a keyless invite (the editor role): set the password and clear the invite, leaving wrapped_data_key NULL. An editor holds no DATA_KEY, so unlike acceptInvite there is no handoff to unwrap or re-wrap — the password only authenticates; it protects no key. The user's role is fixed at invite time and is not changed here.

f
adjustListingIncome

Correct projected listing income to the requested amount.

f
andConditions

Join SQL conditions with AND while preserving their argument order.

f
anyHiddenPackageGroup

Whether any of the given group ids names a HIDDEN package group (hide_package_listings). A hidden package collapses its members to the package name on every buyer surface, so a member there can never gate its own add-on children — the selector would name them.

f
anyListingInPackageGroup

Whether any of the given listings is a member of a package group. Empty input → false (no query). Used to keep a package member from being turned into another listing's required child (a package page can't render child edges).

f
assignListingsToGroup

Add listings to a group (membership rows), ignoring any already present.

f
attendeeDependentDeleteStatements

Build the common dependent-row deletes for one or many attendee ids.

f
attendeeIdByLedgerEventGroup

The id of the attendee whose booking owns this ledger event group, or null when none does. The single-batch booking write stamps every one of an attendee's listing_attendees rows with the booking's ledger_event_group (in the same batch that posts the legs), so a paid session's event group resolves back to exactly the attendee it created. This lets an idempotent replay recover the existing booking from the durable ledger after the (prunable) processed_payments idempotency row has gone — without it, a replay whose legs already exist would be mistaken for a capacity failure and refund a live ticket.

f
buildAttendeeInsert
No documentation available
f
buildCapacityCheckedInsert

Build an INSERT into listing_attendees, capacity-checked by default.

f
buildInputKeyMap

Build input key mapping from DB columns snake_case DB column → camelCase input key

f
buildPiiBlob

Build a PII blob JSON from contact fields. An unpinned latitude/longitude ("") is left out of the JSON so blobs without a pin stay as small as before.

f
bumpSettingsVersion
No documentation available
f
cachedEntityTable

Wire a keyed cache to an id-table in one step: build the cache, register it for the debug-footer stats, and register it with the table→cache invalidation registry so any write to the table (or to a dependsOn table whose triggers feed it — e.g. listings depend on listing_attendees) clears the cache automatically at the db-client layer. Centralises the create-cache + register trio that listings and groups would otherwise each repeat. Cached lets the cache hold a richer row than the table writes (e.g. listings cached with attendee counts).

f
cachedTable

Bundle a request-scoped cache around a table.

f
checkBatchAvailabilityImpl

Check a whole booking batch in one preflight query.

f
checkGroupCapAfterDurationChange

After a duration change on a grouped listing, check whether any day in any existing booking's new range now exceeds the group cap. Returns the earliest over-capacity day, or null if everything fits. Call AFTER recomputeListingBookingRanges so end_at is already updated.

f
checkLinesCapacity

Check several capacity conditions in one query.

f
checkListingAvailability

Check one listing's availability, including its group limits.

f
checkoutStageDeleteStatement

Delete checkout stages for one or many attendee ids.

f
clearAllCaches

Clear every module-level in-process cache.

f
clearLoginAttempts

Clear login attempts for an IP on successful login. Clearing is login-only: successful API-key, booking, and address requests must retain their counters.

f
clearSessionTokens

Clear stored ticket tokens for a session (after redirect has consumed them)

f
cloneGroupMembershipStatement

One group_listings row for a DUPLICATED group, resolving both the new group and the cloned listing by the slug_index each was just inserted with, so the whole clone (group + listings + memberships) runs as one batch — one round-trip, atomic, and clear of the interactive-transaction round-trip guard. Carries the source member's per-package quantity; the flat price override lives in listing_prices and is copied separately (keyed to the new group).

f
columnMapByIds
No documentation available
f
computeGroupSlugIndex

Compute slug index from slug for blind index lookup

f
computeSlugIndex

Compute the blind index used for listing slug lookups.

f
contactFields

Extract ContactInfo fields from an object

f
copyPackageMemberOverridesTx

Copy the source's package overrides onto the duplicate's membership rows in the SAME transaction that inserted them (the create write's afterWrite), so a failure rolls the whole duplicate back rather than leaving a live member at the default price. The flat group and per-day group_day price rows are copied only for package groups the NEW listing actually joined (the duplicate form may untick some of the source's groups) — scoping each source row's encoded group to the clone's group_listings, exactly as the quantity copy does. Otherwise a copied override for a non-joined group would lurk invisibly and resurrect the source's price if the clone were later added to that package.

f
countDatabaseRoundTrip

Count one actual libsql client call and stop before call 51 reaches the network. Unlike advisory N+1 reporting, this stays hard in production because Bunny would reject the same request immediately afterwards anyway.

f
countRows

Count all rows in a table. table must be a trusted constant, not input.

f
createInvitedUser

Create an invited user (no password yet, has invite code). When the inviter passes a wrapped DATA_KEY handoff, the invitee self-activates at /join under the v2 scheme; otherwise an admin activates them later (legacy v1 path). kek_version is a placeholder here — there is no wrapped_data_key until activation, which sets the real version.

f
createKeyedCache
No documentation available
f
createSession

Create a new session with CSRF token, wrapped data key, and user ID Token is hashed before storage for security

f
dateToStartEnd

Convert a nullable date to the stored half-open range.

f
daySpan

Half-open span covering a non-empty set of YYYY-MM-DD days.

f
decryptAdminLevel

Decrypt a user's admin level

f
decryptAttendeeFields

Decrypt attendee fields from the PII blob. Requires migration to be complete (admin is gated behind migration). When paidListing is false, payment_id and refunded are skipped.

f
decryptAttendeeOrNull

Decrypt a single raw attendee, handling null input. Used when attendee is fetched via batch query.

f
decryptAttendees

Decrypt a list of raw attendees (all fields). Used when attendees are fetched via batch query.

f
decryptListingWithCount

Convert a projected DB row and overlay the effective listing defaults.

f
decryptPiiBlob

Decrypt a PII blob and extract all contact fields

f
decryptSessionTokens

Decrypt the ticket_tokens field from a processed payment record. Returns the plaintext token string (e.g. "tok1+tok2") or empty string.

f
decryptUsername

Decrypt a user's username

f
defineCachedListTable

Define a cached "list" table in one call: build the table with defineTable, then wrap it in cachedTable whose fetchAll selects and decrypts every row in orderBy sequence. Returns the cached table plus its getAll/invalidate.

f
defineIdTable

Helper for tables whose primary key column is id.

f
defineTable

Define a table with CRUD operations

f
defineTableProjection

Define an explicit physical-column projection and reuse the table's read transforms without loading or decrypting the rest of the row.

f
deleteAllSessions

Delete all sessions (used when password is changed)

f
deleteAllStaleReservations

Delete all stale reservations (unfinalized, outcome-less, and older than STALE_RESERVATION_MS). Called from admin listing views to clean up abandoned checkouts. Rows carrying a recorded terminal failure are kept so a late redirect/webhook replays the handled outcome rather than re-refunding.

f
deleteAttendee

Delete an attendee and all its listing links, payments, and answers.

f
deleteByField

Delete rows matching a field value

f
deleteByFieldBatch

Delete rows from multiple tables in a single batch transaction

f
deleteByFieldStatement

Build the DELETE statement for one DeleteByFieldTarget — for batches that mix these deletes with other statements.

f
deleteListing

Delete one listing and its listing-owned relationships in one batch.

f
deleteOtherSessions

Delete all sessions except the current one Token is hashed before database comparison

f
deleteSession

Delete a session by token Token is hashed before database lookup

f
deleteUser

Delete a user and all their sessions and API keys

f
enableFooterDebug

Allow the admin debug footer to render the captured queries (staff-only).

f
enableQueryLog

Enable query logging and clear previous entries

f
encryptAttendeeFields

Encrypt attendee fields into a PII blob.

f
encryptedNameSchema

Shared encrypted name column for tables that store a display name.

f
encryptedSeoContentSchema

Shared encrypted SEO/content columns for operator-authored pages (site pages, news posts): the markdown body plus the meta pair.

f
encryptedSlugSchema

Encrypted slug + its plaintext blind-index slug_index (the permalink pair shared by pages and news posts).

f
encryptPiiBlob

Encrypt a PII blob JSON string with the public key

f
encryptTicketTokens

Encrypt ticket tokens for the atomic payment finalize.

f
enforceTransactionRoundTripGuard

Count a statement within one interactive transaction and fire once, exactly when the running count crosses the threshold. Only enforced inside a request scope — startup migrations rebuild tables in one big transaction outside any request, so they are never counted. count is the running per-transaction statement count.

f
envNameSource

A table's env-key-encrypted name column as an id → name source. The env decrypt and the name column are the common case, so per-table wrappers bind just the table and its singular-word alias, then take .byIds (narrow id lookups) or .all() (every name, for pickers/labels).

f
executeBatch

Execute multiple write statements, discarding results.

f
executeBatchWithoutCacheInvalidation

Write without firing cache invalidation. Reserved for plaintext bookkeeping rows (script-version markers) no cache ever holds — written concurrently with requests, the normal path would wipe the settings snapshot the request just loaded.

f
executeUpdate

Build the update statement and run it in one call — the executing form for the single-statement call sites (batch and transactional callers use update itself).

f
executeWithoutCacheInvalidation

Run a single statement without table-scoped cache invalidation.

f
expandDailyRange

Expand a daily-listing range into individual day strings.

f
extractUpdateColumns

Parse the column names assigned by an UPDATE SET clause. Returns a lower-cased Set, or null if the SET clause cannot be found. Each col = expr left-hand side is extracted; commas inside parentheses are skipped so subexpressions don't split assignments. If extraction yields no columns the caller falls back to unconditional invalidation. Exported for unit testing; not part of the public db-client API.

f
finalizeSessionIfUnresolved

Heal a still-unresolved reservation by stamping attendee_id, leaving ticket_tokens untouched. The ledger-replay path uses this: when a late delivery finds the booking already recorded in the ledger, it points its fresh reservation row at the existing attendee so the next delivery takes the fast already-processed path — but ONLY while the row is unresolved, so it never overwrites the attendee_id or blanks the ticket_tokens a racing delivery may have just finalized and stored. Guarded on UNRESOLVED_RESERVATION (the first outcome wins), and a no-op if the row was pruned away.

f
generateUniqueGroupSlug

Generate a unique group slug, retrying on collision.

f
getActiveHolidays

Get active holidays (end_date >= today) for date computation (from cache). "today" is computed in the configured timezone.

f
getActiveListingStats

Get aggregated statistics for active listings. All three values are summed from the precomputed aggregate columns on ListingWithCount (trigger-maintained), which are already in memory from the caller's getAllListings() fetch — no additional DB query needed.

f
getAllActivityLog

Get all activity log entries (most recent first)

f
getAllAttendeePiiBlobs

Get every attendee's encrypted PII blob (one row per attendee). Used to resolve bulk-email recipient lists, where only the email inside each blob is needed. De-duplication of addresses happens after decryption.

f
getAllGroupNames

Narrow id → name map for every group (selects + decrypts only the name), for pickers/labels that must not load the whole groups cache.

f
getAllListingOptions

Read the narrow listing option projection used by item pickers.

f
getAllListings

Read every listing with effective defaults and aggregate projections.

f
getAllSessions

Get all sessions ordered by expiration (newest first)

f
getAllUsers
No documentation available
f
getAttendeeActivityLog

Get activity log entries for a specific attendee (most recent first), decrypting messages.

f
getAttendeeBookingRowsByTokens

Look up attendees by plaintext tokens for the Previous bookings table.

f
getAttendeeKindsByIds

Bounded id → kind lookup for attendee-linked admin surfaces. Empty ids ⇒ empty map. Unknown/deleted ids are omitted.

f
getAttendeeNamesByIds

Bounded id → name lookup for the given attendees, decrypting only the name from each PII blob with the owner private key (no booking join, one row per attendee). Empty ids ⇒ empty map. Used for link labels in the activity log; a deleted attendee's id simply has no entry.

f
getAttendeeOrNull

Get an attendee by ID (decrypted) Requires private key for decryption - only available to authenticated sessions

f
getAttendeePackageRowsRaw

One attendee's raw booking rows within one package group (real lines only — quantity > 0). Lets a listing-scoped action rehydrate the WHOLE package the selected line belongs to, so a per-member notification resend doesn't treat a single member row as the complete package.

f
getAttendeePiiBlobForToken

Get the encrypted PII blob for the attendee identified by a plaintext ticket token. Used to resolve a single-attendee bulk-email recipient. Ticket tokens are unique, so this matches at most one attendee; returns null when the token matches none, so a stale or unknown token resolves to no recipient rather than erroring.

f
getAttendeePiiBlobsForListings

Get the encrypted PII blobs for attendees booked onto any of the given listings (one row per attendee, even if booked onto several of them). Returns an empty array when no listing IDs are supplied.

f
getAttendeeRaw

Get an attendee by ID without decrypting PII Used for payment callbacks and webhooks where decryption is not needed Returns the attendee with encrypted fields (id, listing_id, quantity are plaintext)

f
getAttendeesByIds

Get attendees by ID without decrypting PII, one row per (attendee, booking). Used by the agent run sheet, which already knows the attendee ids it needs and only reads each attendee's contact fields. Returns an empty array for no ids. Decrypt with decryptAttendees before display.

f
getAttendeesByListingIds

Read raw attendees attached to any requested listing.

f
getAttendeesByTokens

Look up attendees by plaintext tokens, returning full booking data. Two queries: attendees by token index, then all listing_attendees for those attendees. Returns results in the same order as input tokens. Bookings sorted by start_at then listing_id for deterministic ordering.

f
getAttendeesPage

Get one page of attendees — with every one of their booking rows — for the admin attendees browser.

f
getAttendeesRaw
No documentation available
f
getCatalogListings

Read only active, effectively visible listings for the public catalog.

f
getCurrentSettingsVersion

Read the current settings_version counter straight from the DB (bypassing the snapshot and the read audit — it is cache machinery, not an app setting). The row is an integer once any write has created it; before the first write (a fresh database) it is absent, which reads as version 0.

f
getDailyListingAttendeeDates

Read every occupied date across daily listing bookings.

f
getDailyListingAttendeesByDate

Read daily-listing attendees whose booking overlaps one date.

f
getDatelessGroupRemaining

Date-less remaining for capped groups reached from cumulative listings.

f
getDb

Get or create database client

f
getGroupBySlugIndex

Get a single group by slug_index (from cache)

f
getGroupPackagePrices

Every membership row for a group, carrying its package_price override and per-package quantity. A null package_price means "no override — use the listing's own price", 0 means explicitly free in this package, and a positive value overrides the price; quantity defaults to 1. The override is read from the group dimension of listing_prices; quantity from the membership row.

f
getGroupPackagePricesByGroupIds

The membership rows for several groups in one query, keyed by group id, so a list endpoint can hydrate every group's package members without a per-group round-trip. Groups with no membership rows are absent from the map.

f
getGroupRemainingForListing

Remaining group capacity for one listing, or undefined when no cap applies or the listing does not exist.

f
getGroupRemainingForSpan

Tightest remaining group capacity over a whole daily span.

f
getGroupsById

Every group keyed by id, from the request-cached set — the batched alternative to one findById per id when resolving or validating many groups without tripping the N+1 read guard.

f
getGroupStaticCapByGroupId

Static maximum capacity for each capped group.

f
getHiddenPackageMemberIds

Of the given listing ids, those that belong to a HIDDEN package — a package group (is_package = 1) with hide_package_listings = 1. Buyers must never meet these members standalone: the package name is the only public surface, so every buyer-facing discovery/direct path drops them.

f
getListingActivityLog

Get activity log entries for an listing (most recent first)

f
getListingAggregateRecalculation

Compare stored listing aggregates with the values rebuilt from bookings.

f
getListingOfferFlags

Read the flags that decide whether one listing may be offered.

f
getListingPickerNames

Read names and offer flags for the admin site-page picker.

f
getListingRemainingForRange

Remaining bookable units for each listing over a date range.

f
getListingRows

The one reader every listing-record surface uses: declare the filter and the order, and it returns raw rows. Encrypted columns are still encrypted — decrypt with the readers in records.ts before display.

f
getListingsByGroupIds

Members of SEVERAL groups at once, keyed by group id — the batched form of the single-group loaders for a multi-group surface. A page with many group leaves would otherwise run one member query per group; this loads the join once and the member listings once, then assembles each group's list in memory. Every requested group id maps to an entry (empty when it has no matching member). activeOnly keeps just active members (the site-page nav's liveness gate); the default includes inactive members (the validators' group-compatibility read for a listing that joins many groups, kept batched to stay under the N+1 guard).

f
getListingsById

Read every listing keyed by id.

f
getListingsBySlugs

Read listings by slug in input order, retaining nulls for missing rows.

f
getListingsWithCountsByIds

Read listings in input order, retaining nulls for expected missing rows.

f
getListingWithActivityLogOrNull

Get listing and its activity log in a single database round-trip. Uses batch API to reduce latency for remote databases.

f
getListingWithAttendeeRaw

Read one listing and one attendee in one round-trip.

f
getListingWithAttendeesRaw

Read one listing and all its attendee rows in one round-trip.

f
getListingWithCount

Read one listing when absence is expected.

f
getListingWithCountBySlug

Read one listing by its plaintext slug when absence is expected.

f
getListingWithCountPrimary

Read a just-written listing from the primary, or null if it was deleted.

f
getNewestAttendeesRaw

Get the newest attendees across all listings without decrypting PII. Used for the admin dashboard to show recent registrations.

f
getPackageDisplaysByIds

The package displays for a set of (possibly repeated or zero) package_group_ids — only ids naming a live package appear in the map. Lets the ticket view collapse each token's package rows into one card per package, so an attendee holding both a package booking and a standalone one (e.g. after an attendee merge) doesn't fall back to per-row cards that leak a hidden member. Groups are resolved together from their shared cache.

f
getQueryLog

Return a snapshot of all logged queries

f
getQueryLogStartTime

Return the start time recorded by enableQueryLog()

f
getSession

Get a session by token (with 10s TTL cache) Token is hashed for database lookup

f
getSharedGroupCapacities

Group remaining, static caps, and membership for date-less package checks.

f
getStoredListingsWithCountsByIds

Read requested listings' stored values without overlaying inherited defaults.

f
getStoredListingWithCount

Read one listing's stored values without overlaying inherited defaults.

f
getUserAuthFieldsById

Get the minimal encrypted user fields needed to authenticate a session.

f
getUserById

Get a user by ID (from cache)

f
getUserByInviteCode

Find a user by invite code hash Scans all users, decrypts invite_code_hash, and compares

f
getUserByUsername

Look up a user by username (using blind index, from cache)

f
getUserDisplayFields

Get the minimal encrypted user fields needed to show assignable users.

f
groupExists

Does a group row exist? The add-item revalidation's single-row check — no name decryption, never the whole table.

f
groupListingTypeError

The in-memory core of validateGroupListingType: given a group's already-loaded members, return the homogeneity error (or null). Callers that validate many groups at once batch the member reads (see getListingsByGroupIds) and drive this directly, so they never issue one sibling query per group and trip the N+1 read guard.

f
hasActiveBookingLine

True when the attendee has a real (quantity > 0) booking on the exact listing. Authorizes per-(attendee, listing) actions — e.g. the signed attachment download — against the EXACT row, not getAttendeeRaw's arbitrary left-joined sibling row (which for a mixed attendee could pass on a ghost/other-listing row, or wrongly reject a valid real-line download). A no-quantity sentinel line is excluded, so a line later marked no-quantity stops authorizing.

f
hashInviteCode

Hash an invite code using SHA-256

f
hasPackageBookings

Whether any booking row is stamped with this package's group id — sold tickets whose display (and hidden-member concealment) resolves through the live package row. Refund placeholders (quantity 0) don't count.

f
idAndCreatedSchema

Shared generated id + plaintext created stamp columns. created stays unencrypted so SQL can order and prune by time without decrypting.

f
incrementAttachmentDownloads
No documentation available
f
initDb

Initialize database tables for an existing database. Fresh database creation requires allowMissingSettings. Uses an advisory lock to prevent concurrent migrations.

f
inPlaceholders

Build SQL placeholders for an IN clause, e.g. "?, ?, ?"

f
insert

Build an INSERT statement from a table name and column→value record.

f
invalidateInitDbCache

Forget the per-isolate "database is ready" cache.

f
invalidateListingsCache

Clear the listing entity cache.

f
invalidateUsersCache

Invalidate the users cache (for testing or after writes).

f
isDatabaseRoundTripLimited

Whether database calls are currently subject to Bunny's per-request cap.

f
isFooterDebugEnabled

Whether the admin debug footer may render the captured queries.

f
isGroupSlugTaken

Check if a group slug is already in use. Checks both listings and groups for cross-table uniqueness.

f
isHiddenPackageMember

Whether a single listing is a HIDDEN package's member — the one-listing form of getHiddenPackageMemberIds, for the buyer-facing guards (API lookup, QR booking, standalone-page test) that ask this of one listing at a time.

f
isInviteExpired

Check if a user's invite has expired. Callers should skip this for users who have already set a password.

f
isInviteValid

Check if a user's invite is still valid (not expired, has invite code)

f
isReservationStale

Check if a reservation is stale (abandoned by a crashed process)

f
isSessionProcessed

Check if a payment session has already been processed

f
isSlugTaken

Check whether a slug is already used, optionally excluding one listing.

f
isUnresolvedReservation

True when a row is an in-progress reservation with no recorded outcome — the in-memory mirror of the UNRESOLVED_RESERVATION SQL predicate.

f
isUsernameTaken

Check if a username is already taken

f
joinStatements

Combine several { sql, args } pieces into one statement: the SQL fragments joined with joiner, the args concatenated in the same order. For SQL built from repeated sub-clauses (e.g. one capacity clause per day, joined with " AND ").

f
lineKeyFromBooking

Build the canonical line key from a stored booking row (matches the ${listingId}|${startAt}|${parentListingId}|${packageGroupId} identity carried by the form's hidden key field). parent_listing_id distinguishes the two rows produced when the same child is booked under two different parents; package_group_id the rows produced when the same listing is booked through two packages (or a package plus its own standalone row) in one order.

f
listingAttendeeRowColumnsFrom

Columns for a ListingAttendeeRow read straight from one listing_attendees source. The source name feeds correlated ledger subqueries, so a caller can pass either the table name or a query alias without the sibling subquery shadowing bare column names.

f
listingStatement

A queryBatch statement (SQL + bound args) for a listing read: the single place a declared query becomes runnable SQL. getListingRows runs it; the activity-log reader embeds it in a batch, and the read-your-own-write reader runs it against the primary.

f
loadAttendeeRows

Load attendee rows carrying the standard ATTENDEE_FIELDS set (PII still encrypted — decrypt before display). Callers vary only in join, order, and where, so the field set is declared in exactly one place.

f
loadExistingLines

Read all current listing_attendees rows for an attendee, with line keys.

f
loadPackageMemberPricing

A package group's full pricing state in one load: its membership rows, the flat override + quantity maps (packageMemberMaps), and each customisable member's per-day overrides — the shape the booking flow, the webhook payload, and the payment revalidation all consume.

f
logActivity

Log an activity. Optionally associate it with a listing and/or attendee so admin views can filter the log by either. A caller may pass its open write transaction so the activity and the action it records commit together.

f
logCompletedSql

Mirror the debug footer to the system logs: emit each SQL statement as it completes, with its bound values omitted. The statement is parameterised, so the string carries only ? placeholders — never PII or secrets — exactly the value-free view the admin footer renders. Whitespace is collapsed so a multi-line statement logs on one line. Routed through logDebug (category "SQL") so it honours the same debug-log suppression as other debug output; the dynamic import avoids the static cycle (query-log is imported by the db client, which the logger transitively depends on), mirroring notifyN1Violation.

f
makeIpRateLimiter

Build a namespaced per-IP limiter: isLimited checks the lockout, record counts one attempt (locking out at maxAttempts for lockoutMs and returning true once locked). Each caller picks its own prefix so counters never collide across features.

f
mapByIds

Run an integer-keyed lookup query and turn each row into a [key, value] pair via toEntry, returning the id-keyed map (empty when ids is empty).

f
markSessionFailed

Record a handled terminal failure on a still-unresolved session. A later redirect/webhook for the same session reads this back via parseSessionFailure and returns the same outcome, so refunds and validation never run twice. Guarded on UNRESOLVED_RESERVATION, so it never clobbers a finalized success and never overwrites an already-recorded failure (the first outcome wins); a no-op if the row was pruned away.

f
migrateUserToV2Kek

Re-wrap a user's DATA_KEY under the password-bound (v2) KEK. Called at login — the one place both the raw password and the freshly-unwrapped DATA_KEY are in hand — for users still on the legacy v1 wrap, replacing the DB-recoverable wrap in place without touching any encrypted data.

f
nameSource

A table's id → name projection, bound to its columns once. byIds returns the map for the requested ids (empty ids ⇒ empty map); all returns it for every row, ordered by id. Only the name column is decrypted, via the decryption-agnostic decryptName; table/alias/nameColumn (alias qualifies the selected columns, repo SQL convention) are internal constants.

f
onUsersInvalidated

Register a callback to run whenever the users cache is invalidated.

f
overlapsDay

Whether a stored booking overlaps one day. String comparison mirrors the SQLite overlap check byte-for-byte.

f
packageChildEdgeConflict

Which package invariant adding child edges would violate, or null when the edges are fine: gate_in_hidden when the parent is joining/in a HIDDEN package group (a visible package renders the member's child selector, so a member gating children is fine there), or child_is_member when any chosen child is itself a package member (a package member is only ever sold as part of its bundle, never folded under another parent). An empty childIds (clearing children) is never a conflict.

f
packageDisplaysForRows

The package displays behind a set of booked rows — each row's attendee names its persisted package_group_id (0 on a plain row, matching no package). Shared by the ticket view, the wallet lookup, and the email renderer, which all carry { attendee, listing } row shapes.

f
packageMemberMaps

A package group's member rows projected into the two maps every consumer needs (the booking flow, the webhook revalidation, the bookability gate, and the test harness): prices keeps only members with a real override — a positive price OR an explicit free 0, dropping a null "no override" — while quantities covers every member (default 1). Owning both here keeps the "what counts as an override" rule in one place; callers destructure what they use.

f
packageMembersError

The member-naming package error for the first listing in listings that can't be a package member (pay-what-you-want, an add-on of another listing, or — on a hidden package — a member gating its own children), or null when every listing is a valid member. The one place every package save (group form, add-listings, listing form/API, catalog import) turns an unpackageable member into its user-facing message.

f
parsePiiBlob

Parse a PII blob JSON back into contact fields (defaults v to 1 for pre-versioned blobs)

f
parseSessionFailure

Parse a stored terminal failure, or null when the row carries none. We only ever write valid encrypted JSON (via markSessionFailed), but a value that won't decrypt or parse (restore, manual edit, rotated key) must not crash the replay path — it degrades to a generic terminal failure so the session still resolves instead of looping.

f
perDayLoads

Per-day quantity sums from rows fetched for the whole span.

f
queryAll

Query all rows, returning a typed array.

f
queryAndMap

Execute a SQL query and map result rows through an async transformer.

f
queryColumnSet

Run a single-column SELECT and collect that column's values into a Set of strings — the shared shape of the "which hashes/names already exist" reads (e.g. the live table names, the unsubscribed contact hashes).

f
queryIdColumn

Run a query whose single selected column is aliased id and return the ids.

f
queryOne

Query one row, or null when the query returns none.

f
queryOnePrimary

Query an optional row on the primary (read-your-writes). Use this to read a row back immediately after committing its own write: a plain queryOne runs in "read" mode, which Turso can route to a replica lagging the just-committed write and so miss the row (returning null); routing through queryBatchPrimary ("write" mode) always hits the primary. Mirrors the same guard on syncListingPrices. args is required — every read-back keys on the written row's id.

f
rawSql

Embed a raw SQL expression (e.g. last_insert_rowid())

f
rebuildWipedSchema

Rebuild the full schema on a database that resetDatabase() just wiped, without reading the database to decide what to create.

f
recomputeListingBookingRanges

Recompute end_at on all existing listing_attendees rows for an listing based on a new duration_days value. Leaves NULL-start rows alone. The .000Z suffix matches the format fresh inserts produce via toISOString() so raw-row dumps stay consistent.

f
registerCache

Register a cache stat provider (called at module load time)

f
registerTableInvalidation

Register invalidate to run whenever any of tables is written.

f
releaseReservation

Release an in-progress reservation so the very next delivery can re-claim it. Deletes only a still-unresolved row, so it never clobbers a finalized success or a recorded terminal failure that a racing delivery may have written.

f
remainingByListingOverGroups

Tightest capped-group value for each listing.

f
requireListingsWithCountsByIds

Read required listings in input order through the shared cache path.

f
requireListingWithCount

Read one required listing through the shared many-listing path.

f
requireOne

Query one required row and name the failed query when none exists.

f
requireOnePrimary

Query one required row from the primary.

f
reserveSession

Reserve a payment session for processing (first phase of two-phase lock) Inserts with NULL attendee_id to claim the session. Returns { reserved: true } if we claimed it, or { reserved: false, existing } if already claimed.

f
resetAggregates

Reset selected aggregate columns from trusted SQL expressions. Each expression must use the entity id as its only placeholder.

f
resetDatabase

Reset the database by dropping all tables (reverse order for FK safety)

f
resetGroupListings

Remove every listing from a group (used when the group is deleted), along with the group's package price overrides — its flat group and per-day group_day price rows key on the group id, so they'd otherwise outlive the deletion.

f
resetListingAggregateFields

Reset selected listing aggregate columns from booking rows.

f
resultRows

Cast libsql ResultSet rows to a typed array (single centralized assertion)

f
rowExists

True when the query returns at least one row. sql should be an existence probe (e.g. SELECT 1 ... LIMIT 1); the selected columns are ignored. Shared by the per-(attendee, listing) and built-site assignment checks so the row-presence boilerplate lives in one place.

f
rowExistsForIdList

Build an existence check for "one leading id, matched against a list of ids". The returned checker binds leadingId to the first ? and expands ids into the IN (...) your buildSql embeds via the placeholder string it receives. Shared by the per-attendee "across these listings" probes so their signature and args boilerplate live in one place. Empty ids still runs the query with an empty IN (), which matches nothing — callers pass a non-empty list.

f
rowsByIds

Run an id-keyed SELECT, short-circuiting to [] (no query) when ids is empty. buildSql receives the bound ?-placeholder list for ids, so ids are the only query args. The base skeleton for the id-map helpers below and for any read that loads rows for a caller-supplied id list.

f
runWithQueryLogContext
No documentation available
f
setDb

Set database client (for testing)

f
setGroupListingsActive

Set the active flag on every listing in a group. Returns the number of listings affected.

f
setGroupPackageMembers

Set a group's package member overrides — the flat group price rows in listing_prices plus the per-package quantity on the membership rows. Pass tx to run inside an existing write transaction (the admin API update path, so the overrides commit atomically with the group row write); omit it to run as the function's own statements. See applyPackageMembers for the partial-update rules.

f
setListingGroupsTx

Replace a listing's group memberships inside an existing write transaction, so the change commits atomically with the listing row write (the admin API create/update path). Mirrors setListingGroups but reads the current set and runs each statement on the caller's tx.

f
setN1GuardNotifyOnly

Switch the N+1 guard between throw (default) and notify-only (production).

f
sqlWallClockMs

Wall-clock milliseconds during which at least one query was in flight: the combined length of the query intervals with overlaps merged.

f
stringColumnSet

Collapse a result's rows to the set of one column's values, as strings — the shared tail of the "which names/ids already exist" reads (applied migrations, live table columns, index and trigger names).

f
toCamelCase

Convert snake_case to camelCase (e.g. max_attendeesmaxAttendees).

f
toSnakeCase

Convert camelCase to snake_case (the inverse of toCamelCase).

f
trackSql

Run an async DB operation, enforcing the N+1 read guard and logging it when footer tracking is active.

f
update

Build an UPDATE statement from a table name, a column→value record for the SET clause, and a column→value record for the WHERE clause (equality checks, ANDed together). The counterpart of insert — use it instead of hand-writing the UPDATE … SET … WHERE … string when every condition is a plain column = value match; a write that needs a richer guard (IS NULL, an inequality, a subquery) keeps its own SQL. SET values may be rawSql expressions (e.g. a counter increment).

f
updateAttendeePII
No documentation available
f
updateAttendeeStatus

Set an attendee's status from the admin edit form (a plain column write, outside the encrypted pii_blob). The outstanding balance is NOT set from the form — it projects from the transfers ledger, and an operator adjusts it through the ledger's manual write-off entries.

f
updateCheckedIn

Set a line's check-in flag, refusing a no-quantity (quantity 0) line — it isn't a real ticket, mirroring the refunded-ticket guard in checkin.ts. The quantity > 0 predicate scopes the write so a ghost row is a no-op (it can never have been checked in, so scoping the check-OUT case too is harmless).

f
updateListingAggregateValues

Manually set every editable listing aggregate.

f
useTransaction

Run work on the caller's open transaction, or open one when there is no caller transaction. Transaction-aware table methods use this so direct calls and larger atomic operations share the same write path.

f
validateGroupListingType

Validate that a listing is compatible with a group's existing listings. Every listing in a group must share both the same ListingType and the same customisable_days setting, so the shared booking form can show a single day-count selector (or none) for the whole group. Returns an error message if mismatched, null if OK. Pass excludeListingId to skip a specific listing (for edit-self case).

f
verifyUserPassword

Verify a user's password (decrypt stored hash, then verify) Returns the decrypted password hash if valid (needed for KEK derivation)

f
withTransaction

Run work inside one interactive write transaction, committing on success and rolling back (then rethrowing) on any error. Use this — rather than a plain batch — when a multi-step write needs conditional logic between steps, e.g. create → check capacity → finalize, where a zero-row guard must abort and undo everything.

f
writeRowInTransaction

Write one row statement in a fresh write transaction and run persist (the coupled join-table writes) on the same tx, so the row and its side writes commit or roll back together. Returns the row id — existingId on update, or the INSERT's lastInsertRowid on create (existingId null). Shared by the REST resource (HTML forms) and CRUD API write paths.

f
writeTableRow

Execute one table-built INSERT/UPDATE on an open transaction and return the affected row. A conditional write returns null when its condition is false.

Interfaces

I
ActivityLogEntry

Activity log entry as callers see it: the message decrypted to plaintext.

I
IntervalRow

A booking row's stored range and quantity.

I
ListingCapacityRow

A listing's identity, capacity, and current booked quantity.

I
NamedSortOrderInput

Input shared by ordered tables whose only required value is a name.

Type Aliases

T
ActiveListingStats

Aggregated statistics for active listings

T
ActivityLogInput

Activity log input for create

T
AggregateRecalculation

Per-column comparison of each aggregate F's stored value against its rebuilt-from-source value — what the "recalculate aggregates" tools return.

T
AggregateValues

Stored values of the trigger-maintained aggregate columns F, keyed by column.

T
AtomicDesiredLine

A desired final-state line for the atomic update path. Re-exported from the shared types module so callers can keep importing it from here.

T
AttendeeBookingRows

PII-free booking rows for a token-resolved attendee.

T
AttendeeInput

Input for creating an attendee atomically (one or more listings)

T
AttendeeSort
No documentation available
T
AttendeesPage

One page of attendee booking rows, plus whether a further page exists. Carries the full field set because the same page query feeds both the browsing table (which shows no money) and the CSV export (which sums price_paid); the table simply ignores the columns it doesn't render.

T
T
BatchAvailabilityItem

Item for batch availability check

T
BookingBatchPlan
No documentation available
T
BrowsingAttendee

A browsing-table attendee row — every core column plus refunded, but none of the expensive money projections.

T
ColumnDef

Column definition for a table

T
CreateAttendeeResult

Result of atomic attendee creation

T
DecryptedAttendeeRow

A decrypted attendee row: the raw row with its PII overlaid and its booleans/price coerced, keeping exactly whichever optional money fields the read selected. DecryptedAttendeeRow<Attendee> is the full Attendee.

T
DeleteByFieldTarget

One delete-rows-matching-a-field target: which table, matched on which field, for which value.

T
ExistingLine

A pre-fetched existing booking row plus its line key.

T
GetListingsQuery

Everything a caller declares to read listing records: which rows to keep and in what order.

T
HolidayInput

Holiday input fields for create/update (camelCase)

T
ListingAggregateField
No documentation available
T
ListingAggregateRecalculation
No documentation available
T
ListingAggregateValues
No documentation available
T
ListingBooking

A single listing booking within a multi-listing attendee creation

T
ListingOfferFlags
No documentation available
T
ListingOption
No documentation available
T
ListingOrder

How the rows come back. A named order so callers can't hand-roll a stray ORDER BY. Exported because a narrow listing read — one that selects its own columns rather than the whole record — still wants to come back in the same order as the full reads.

T
ListingProjectionRow

The raw shape a listing read returns: the stored columns plus the projected values, before decryption and before any inherited defaults are overlaid.

T
ListingWhere

A declarative filter for a listing read. Each present field adds one WHERE clause (absent fields don't constrain), so a caller says WHICH listings it wants rather than hand-writing SQL. An empty filter reads every listing.

T
ListingWithActivityLog

Result type for listing + activity log batch query

T
ListingWithAttendeeRaw
No documentation available
T
ListingWithAttendees
No documentation available
T
ListsByIds

A batch loader: takes a list of ids and returns, for each id, the list of related numbers found for it. Ids with no matches are absent from the map.

T
Migration
No documentation available
T
PackageDisplay

Package-group display info for grouping a booking's lines under the package name on tickets/emails.

T
T
RawAttendeeRow

The raw attendee columns the decrypt step reads and coerces. price_paid and refunded are optional because a field-selected read may leave them out (see file://./select.ts); the decrypt then leaves them out too rather than coercing an absent column into "undefined" / false.

T
ReadColumn

Run one column's declared read transform (e.g. decrypt) on a stored value — identity when the column declares none or the value is null. For reading a single column back without building a whole row. rowId (when known) lets the transform name the record in error reports.

T
ReserveSessionResult

Result of session reservation attempt

T
SchemaRequirement

The schema objects a single migration is responsible for. Drives that migration's verify() so failures name exactly what the migration was meant to add or remove.

T
SettingsData

Full settings snapshot type.

T
SqlStatement

A single SQL statement plus its bound arguments — the object form libsql's batch API accepts. This is the one shared shape for a { sql, args } pair; callers that build statements to hand to executeBatch and friends import this rather than re-declaring the same object type locally.

T
StoredLogMessage

A stored log message: owner-key ciphertext for rows written since the keypair existed, env-key ciphertext for legacy rows the backfill hasn't re-encrypted yet. The format prefix routes decryption at runtime.

T
StoredPaymentFailure
No documentation available
T
StoredTableProjectionRow

A selected row before the table's declared read transforms run. Database values are unknown here because booleans and encrypted strings have a different stored representation from the application's Row type.

T
TableDefinition

The shape that defines a table: its name, primary key, and column schema.

T
TableSchema

Table schema definition Keys are DB column names (snake_case), values are column definitions

T
TxScope

The slice of an open write transaction handed to a withTransaction callback: run statements singly or as one batch; commit/rollback are managed for you.

T
UpdateAttendeeAtomicResult

Result of an atomic attendee update. Every failure carries listingIds — the SPECIFIC listings that failed the capacity preflight — so a caller can tell the operator what was actually sold out instead of a bare reason string. Empty when no particular listing is to blame: a duplicate booking slot (see applyAttendeeAtomicEdit's duplicate-slot guard) or a no_lines rejection.

T
UpdateAttendeePIIInput

Input for updating attendee PII (shared across listings)

T
UserAuthFields
No documentation available
T
UserDisplayFields
No documentation available

Variables

v
activityLogTable

Activity log table definition.

v
ALL_SETTINGS_KEYS

All keys that populate the snapshot plus the setup-complete flag. Equivalent to the former loadAll SELECT * in terms of what affects request behaviour. Use in tests and in pre-load bundles that need every setting.

v
ATTENDEE_LISTING_CONTRIBUTIONS_SQL

Per-listing aggregate contributions of an attendee's lines, summed so the hold-delete restore can add them back after deleting. tickets_count counts only quantity > 0 rows (mirroring the delete trigger, which now subtracts 0 for a no-quantity line — see ticketCountSumExpr); booked_quantity sums over all rows. Exported for the shared-predicate guard test.

v
ATTENDEES_PAGE_SIZE

Attendees per page in the admin attendees browser. Fixed here so the page size is never derived from the request — callers choose only the page.

v
AttendeeSortSchema

Sort order for the admin attendees browser

v
buildCreateUserStatement

Build the INSERT that createUser would run, without executing it, so a caller can include the user creation in a batch/transaction with other writes (e.g. initial setup creates the owner atomically alongside its config keys).

v
CONFIG_KEYS
No documentation available
v
createUser

Create a new (already-activated) user with encrypted fields. Activated users are created at the password-bound KEK scheme (v2); the caller computes the matching wrapped_data_key via wrapDataKeyForPassword.

v
EMAIL_BODY_KEYS
No documentation available
v
execute

Run a single statement: track it for the query log / N+1 guard, then fire any table-scoped cache invalidation. Every single-statement read and write goes through here (queryOne/queryAll wrap it), so cache invalidation is driven by the write itself rather than by each call site remembering to invalidate.

v
executeBatchWithResults

Execute multiple write statements and return their ResultSets. Statements run in order within a single transaction (Turso batch API). Ideal for cascading deletes and multi-step writes.

v
getActiveListingsByGroupId
No documentation available
v
getGroupPerDayRemaining

Per-day remaining for several capped groups, loaded in two queries.

v
getGroupRemainingByGroupId

Remaining capacity for each capped group.

v
getGroupRemainingByListingId

Tightest remaining capped-group capacity for each listing.

v
getListingsByGroupId

Get all listings in a group with attendee counts (including inactive).

v
groupListings

The listing ids in a group, and the reverse listing-to-groups side.

v
groups
No documentation available
v
hasPaidLine

True when any of the listings has a paid line for this attendee — a gross sale leg in the row's ledger_event_group (a sale leg's amount is always > 0, so its existence is exactly a non-zero projected price_paid; a refund keeps the gross leg, so a refunded line still reads as paid). One query over all the IDs, read from the live ledger rather than the edit form's submitted key (a stale/missing key can leave it null), so a recorded payment is never dropped onto a fresh quantity-0 row. Callers pass a non-empty list.

v
holidays

Cached holidays table — name is encrypted, dates are plaintext; writes auto-invalidate the cache.

v
idAndEncryptedNameSchema

Shared columns for tables with a generated id plus an encrypted name.

v
idAndEncryptedSlugSchema

Shared columns for tables with a generated id plus the encrypted slug pair.

v
isAttendeeSort

Type guard: narrows an arbitrary string to an AttendeeSort.

v
LATEST_UPDATE

Schema version label and the migrations bookkeeping table name.

v
LISTING_AGGREGATE_FIELDS
No documentation available
v
LISTING_ATTENDEE_ROW_COLS
No documentation available
v
LISTING_ORDER_SQL
No documentation available
v
listingGroups
No documentation available
v
listingNames

Read and decrypt listing names without loading full records.

v
listingOptionProjection

The shared narrow listing shape used by listing and attribute pickers.

v
listingsTable

Listing CRUD with cache invalidation and listing-price synchronization.

v
loginLimiter
No documentation available
v
N_PLUS_ONE_THRESHOLD

Max times one parameterized read may run as a separate round-trip within a single request before the N+1 guard fires. Set above the worst legitimate repeat in the suite; lower it to catch smaller N+1s.

v
PII_BLOB_VERSION

Current PII blob schema version

v
queryBatch

Execute multiple read queries in a single round-trip using Turso batch API.

v
queryBatchPrimary

Run read queries pinned to the primary in a single round-trip.

v
rawListingsTable

Raw listings table. Records adds cache-aware CRUD and price syncing.

v
SCHEMA_HASH
No documentation available
v
SCHEMA_TABLE_NAMES

Ordered table names — matches FK dependency order (parents before children)

v
setListingGroups
No documentation available
v
settings
No documentation available
v
SNAPSHOT_KEYS

Every config key that maps to a snapshot field, in load order.

v
STALE_RESERVATION_MS

Threshold for abandoned payment reservations in ms (default: 300000 = 5 min)

v
TRANSACTION_ROUNDTRIP_THRESHOLD

Max statements one interactive write transaction may issue before the round-trip guard fires. Every statement inside a withTransaction holds the single primary write connection open for another edge→primary round-trip, so a chatty interactive transaction is what the primary aborts as "Transaction timed-out". A plain batch (executeBatch) is one round-trip regardless of how many statements it carries and is never counted — the whole point is to push chatty writes onto it. Set above the largest legitimate interactive transaction; anything that grows with input size (a big attendee merge, a per-leg ledger post) must prepare its reads outside the lock and apply its writes as one batch instead. The current high-water mark is recreateTable on attendee_answers: 8 DROP TRIGGER + 5 rebuild + 6 CREATE INDEX + 8 CREATE TRIGGER = 27 statements; the threshold sits above that.

v
UNRESOLVED_RESERVATION

A processed_payments row is in exactly one of three lifecycle states, encoded across two columns: reserved (in-progress: attendee_id NULL, no failure_data), finalized (success: attendee_id set), failed (terminal handled failure: attendee_id NULL, failure_data set). This predicate is the single source of truth for the unresolved shape, so the encoding can't drift between call sites.

Usage

import * as mod from "docs/database.ts";