Skip to content

Event Scheduling Architecture

Wavemap models an event schedule as organizer-authored calendar facts plus server-resolved projections. This keeps a show at 20:00 America/Toronto anchored to the organizer’s intent even when an admin, viewer, venue, or server operates in a different time zone.

Use this page when changing event schedule contracts, persistence, queries, authoring workflows, venue suggestions, series membership, exports, notifications, or integrity diagnostics. It describes the foundation that later admin and public UX may rely on; it does not prescribe final components or presentation.

ConcernOwnerDurable Rule
Local date, optional local time, optional end, and reference zoneEvent authoring requestThese are the organizer-authored facts.
Exact start and end instantsBackend schedule resolverThe browser never submits these as authority.
Administrative stateEvent rowscheduled, cancelled, postponed, and rescheduled are authored editorial facts.
Temporal phase and phase basisBackend read queryupcoming, ongoing, and historical are derived for the read operation time.
Venue time-zone suggestionVenue and event relationship compositionA suggestion may inform authoring but never rewrites an event snapshot.
Viewer-local displayLater presentation adapterConversion may explain an event to a viewer without erasing organizer context.

An event owns referenceTimeZoneId even when it has venues. This is important for digital events, unresolved venues, multi-venue events whose suggestions conflict, and venue records that later move or are corrected.

The shared request contract carries one start boundary and an optional end boundary:

{
referenceTimeZoneId: "America/Toronto",
start: { localDate: "2042-05-17", localTime: "20:15" },
end: { localDate: "2042-05-17", localTime: "22:00" }
}

Each boundary independently has date or date-time precision. A date-only boundary has no fake midnight and no exact instant. A repeated local time may carry an explicit earlier or later disambiguation choice; a nonexistent local time is rejected. An end date is inclusive when it has date precision, while an exact timed end must occur after its start.

Read DTOs add the server-resolved exact instant to timed boundaries and add temporal phase plus phase basis. They publish publicId and slug for entity identity and do not publish internal database IDs.

PostgreSQL stores the authored and resolved representations together:

  • referenceTimeZoneId and startLocalDate are required.
  • Local times use time without time zone; exact projections use timestamp with time zone.
  • Date-only boundaries keep their exact projection null.
  • A local time and its exact projection must be present or absent together.
  • An end time requires an end date.
  • Local date ranges cannot reverse, and exact ranges must be positive.

The backend resolver validates the IANA identifier, recognizes daylight-saving gaps and folds, and resolves exact instants before the transaction writes the event. Create, scalar update, schedule update, and relationship update retain normal authorization, revision, provenance, collision-retry, and rollback behavior. A meaningful write increments the owning entity revision once; an idempotent write does not manufacture a revision.

Active event reads use only the native schedule columns. The original epoch-string fields were removed by the native read-cut migration. Historical MongoDB-port values survive only as reviewed seed-source evidence with an explicit conversion manifest; runtime reads do not reinterpret them.

The backend composes page-ready event summaries rather than exposing database rows. Index, typeahead, detail, artist preview, and authoring hydration all share the native schedule DTO.

Query semantics keep two useful orders distinct:

  • Organizer-calendar order uses the authored local date and wall time.
  • Exact chronology uses startsAt and excludes date-only rows that do not have an exact instant.

Temporal phase is computed in SQL from the operation time. A timed event with no end remains explicitly uncertain; the foundation does not invent a six-hour duration. Administrative cancellation remains separate from phase classification.

Event schedules and relationships share transaction and revision rules without becoming one undifferentiated payload.

  • Initial event venues may be submitted during event creation so the event and intended small venue set commit atomically.
  • Later event–venue edits replace the complete desired set through a dedicated revision-aware capability.
  • Initial artist events may be submitted during artist creation; later artist–event edits use one atomic public-ID addition/removal delta suitable for a higher-cardinality collection.
  • Event-series membership groups independently scheduled occurrences. Series state and future generation rules do not supply or cascade occurrence timing.

All relationship requests resolve public target identities at the backend, require active additions, keep join identity inside persistence, and use caller-owned transactions. Wavemap-native quick editors and a future Payload CMS integration must compose these same capabilities rather than receive separate invariant-bypassing write paths.

The backend integrity analyzer compares persisted authored values and exact projections with the current Node/ICU/time- zone runtime. It reports one of:

  • valid
  • projection-drift
  • unresolvable
  • structural-inconsistency

Completeness and repair disposition are independent from integrity state. A report can therefore distinguish a valid date-only event from an invalid timed event without treating missing precision as corruption.

The analyzer may produce an update-compatible, revision-aware candidate. It never mutates data, chooses a fold on the operator’s behalf, or claims that a time-zone database update caused a mismatch that the row cannot prove. Apply an approved repair through the normal protected event update route so authorization, transaction, provenance, relationship preservation, and conflict detection remain intact. See Event Schedule Integrity for the operator path.

The deterministic development seed currently proves imported native schedules plus representative date-only, date- range, timed, unknown-end, DST-fold, online, hybrid, multi-venue, tour-stop, and festival-concept cases. Disposable DB-backed tests migrate, seed, read, diagnose, mutate, and roll back through real PostgreSQL.

That fixture set is proof scaffolding, not the mature distributable corpus. A later database-derived capture workstream must define its artifact and sanitization manifest and must wait for the venue/address roadmap to settle which location values may be stored and redistributed. Private users, credentials, environment URLs, and provider-restricted data must not enter that artifact implicitly.

The public GET /api/v1/events/:eventID/calendar.ics route loads current active Event details and serializes the existing loss-aware calendar projection. The historical eventID parameter carries the public ID. Its strict query accepts only an optional locale; no schedule or canonical URL is accepted from the caller. The frontend owns download/loading/error feedback and must explain that later Event changes do not update imported copies.

Success is raw text/calendar; charset=utf-8, with an attachment filename of wavemap-event-<publicId>.ics. Content-Disposition is exposed through CORS, Content-Language identifies the selected export language, and Cache-Control: no-store applies to success and errors. An explicit locale takes priority over browser language; supported base languages are resolved from region tags and English is the fallback. JSON API error envelopes retain the standard header-based error localization: invalid input is 400, missing/inactive Events are 404, and unsupported mixed precision is 422 with event.calendar.unsupported-schedule.

The backend derives the canonical URL from its configured frontend origin, selected locale, current slug and public ID. The calendar UID is <publicId>@<canonical-hostname>, stable across renames, locale changes and schedule edits; changing the configured hostname changes that namespace. DTSTAMP marks the generated export snapshot, not an Event modification timestamp or a promise to synchronize existing imports. The file has no subscription, recurrence, invitation, attendee or organizer workflow.

Serialization follows RFC 5545: CRLF lines, UTF-8 folding at 75 octets, escaped TEXT values, DATE starts and exclusive DATE ends, or exact UTC DATE-TIME boundaries. UTC serialization preserves the resolved instant, including DST folds; it does not retain a floating wall time. Unknown ends omit both DTEND and DURATION and append localized “End time not announced” copy without dropping the authored description. Calendar clients may display default durations; those are not authored Event facts. Source schedules remain unchanged, and mixed-precision boundaries produce no file.

The production serializer has focused wire-format proof and the public endpoint has request and disposable PostgreSQL proof, including current edits and hard-deleted Events. Reproducible synthetic import samples are generated by apps/wavemap-back-end/test/manual/calendar-export/generate.ts. Actual Windows/Outlook and Linux/Evolution imports remain a separate manual proof gate; successful serialization or download alone does not establish client import behavior.

Later UX work may rely on these settled boundaries:

  • Organizer-authored wall values and an explicit IANA reference zone are request authority.
  • Exact projections and temporal classification are backend authority.
  • Public event reads and writes use shared DTOs and public entity IDs.
  • Event and artist relationship writes are atomic, authorized, revision-aware, and presentation-independent.
  • Venue zones are advisory; events retain their own scheduling snapshot.
  • Integrity scans are read-only, privacy-conscious, deterministic for one runtime, and repair through normal mutations.
  • Date-only, mixed-precision, online, hybrid, unknown-end, and multi-zone cases are first-class rather than edge adapters.

The following remain separate product or architecture work: polished admin authoring, public schedule presentation, complete client validation, recurrence generation, festival hierarchy, venue-address migration, spatial queries, Payload CMS integration, and representative-corpus capture automation.

Review this page when the event schedule DTO, native columns or constraints, resolver ownership, temporal classification, relationship mutation shape, integrity report schema, series semantics, or venue-snapshot rules change.

  • packages/api-contracts/src/types/events/
  • packages/data-access/src/schema/events.ts
  • packages/data-access/src/reads/events/
  • apps/wavemap-back-end/src/utils/eventSchedule.ts
  • apps/wavemap-back-end/src/utils/eventScheduleIntegrity.ts
  • apps/wavemap-back-end/src/queries/events/
  • apps/wavemap-back-end/src/handlers/events/
  • apps/wavemap-back-end/src/db/seeds/dev/eventScheduleSeed.ts
  • apps/wavemap-front-end/src/utils/events/
  • apps/wavemap-front-end/src/app/[locale]/(events)/