Skip to content

@guestcraft/server

The composition root wires API/auth/core to database and remote adapters. http starts explicitly with validated config, injected clock/randomness and owned pools; importing it performs no I/O. migrate alone uses owner credentials. Environment reads belong in config/environment.ts.

The HTTP runtime composes persisted property, inventory, guest and reservation use cases. See reservations. Calendar/front-desk are local read models, populated by direct bookings and the Channex import projection. Outbound ARI is composed with explicit takeover and automatic bounded recovery.

pnpm --filter @guestcraft/server test:integration verifies the composed HTTP flow on PostgreSQL/PgBouncer, including rollback, roles, persistence and schema revisions. pnpm quality runs the complete suite and coverage gates.

The composed tests also exercise previews without writes, summary/history reads, free-unit choices, housekeeping, archive recovery, same-day undo and current ETags on stale guest/reservation edits.

The HTTP runtime wires identity-aware team operations into the public API, while keeping public signup closed and blocking legacy membership/invitation bypasses. See ADR 0030.

The runtime also composes local rate setup, effective calendar reads, quotes and atomic ARI outbox writes (ADR 0027). The transaction-bound outbox also wakes the ARI publisher; production ownership is checked before writes. The worker export composes Channex feed workers for resident Docker and budgeted cron execution. pnpm --filter @guestcraft/server worker starts the resident entry point. Production ownership gates and preview refusal protect acknowledgements and webhook registration. See booking import.

The HTTP composition wires performance, history, pickup, cash and folio revenue queries to transaction-bound reporting aggregations. Reports are read-only and role-guarded. Performance/pickup use frozen booked prices; financial reports use ledger service dates (ADR 0031).

Reservation folios and manual settlement are documented in the folio workflow and ADR 0029. The ledger, frozen room charges, property extras, manual payments/refunds, a check-out that settles only at the last room and never holds a credit (an owed balance needs settlement or an audited administrator approval), refunds due, net and full-ledger views and a non-fiscal statement are implemented. Taxes need a country module on the TaxCalculator port (with a re-rate operation); fiscal documents and online providers remain planned.

Guarded ARI publication has rate-plan mapping, numbered property leases, booking barriers, shared pacing and health/resync with explicit takeover. Automatic catch-up, bounded unknown repair, provider request lookup and nightly drift repair (the channex.ari_drift dispatch wakes due publishers) are implemented; channex.alerts reconciles alerts and channex.history_import runs historical booking imports (ADR 0048). See the ARI workflow.

On Vercel production, the HTTP bridge supplies Next.js after() to createHandler. Committed ARI/feed requests wake a budgeted worker after the response; rollback, reads, development and previews do not. Docker keeps its resident worker, and the authenticated daily cron remains a safety net. See ADR 0034 for coalescing, lifetime and recovery limits.

Property policies, immutable sold conditions, simulated cancellation fees, explicit no-show/recovery, guarantee attestations and prepayment tracking are composed with the folio ledger. See the policy workflow and ADR 0032 for permissions, deadlines, migration semantics, SDK operations and planned OTA reporting/provider capabilities.

HTTP, cron/request-pump and worker composition share an injectable business clock. createBookingWorker accepts it as its fourth argument; the production default is Temporal system time. PostgreSQL owns delivery/lease/pacing deadlines independently.

Channex Messages shares the production request after() pump with booking import and ARI. Committed jobs wake a bounded worker; only verified webhooks with a message event wake the messages drain. The safety job enqueues booking drains first and then only schedules messages drains, so a messages failure never stops booking recovery; Docker retains its resident worker. Message composition notifies only after commit, formats composer dates for the template language, and delays the drain job for a reply waiting on a backoff. credentials:reencrypt also rewrites message records.

HttpRuntimeOptions.requestWorker injects only the after-response worker factory. The member-publication regression keeps real PostgreSQL/pg-boss/Channex handlers while disabling idle waiting and advancing the injected gathering clock. Normal composition uses the production worker and unchanged pump budgets.

The deployment command verifies existing role logins before schema writes, under the same lock as grants and queue registration. Both Docker and Vercel build logs print safe MigrationError/BootstrapError diagnostics; unclassified errors use migration.unexpected_failure. See the database diagnostic codes and rotation/recovery procedure. Migration connections need one PostgreSQL session: the direct URL (on Neon, the unpooled one) or a session pooler (on Supabase, port 5432). The command refuses a transaction pooler with migration.session_required, using a probe connection with the same URL, and refuses the known transaction-pooler addresses in its configuration. New installs provision an independent GUESTCRAFT_DATABASE_ROLE_SECRET; legacy installations retain auth-derived passwords until pinned.

http/mcp.ts serves @guestcraft/mcp at /api/mcp (ADR 0047), stateless, on Vercel and Docker alike. Each request is authenticated first by the instance’s credential gateway, at the configured resource URL and without its cookie; client sessions and device tokens are refused, and a refused request is challenged toward <base>/.well-known/oauth-protected-resource/api/mcp. Tools call the composed app in process: each request they make is registered with the caller’s resolved principal, which the API’s principal resolver uses before authenticating, so no bearer is forwarded and no instance key is used. Confirmations use a key derived from AUTH_SECRET and the guestcraft_meta.mcp_confirmations ledger, shared by every instance. See the MCP guide.

The HTTP composition always installs the shared cookie-origin policy before all API routes, including team and identity writes. POST, PUT, PATCH and DELETE require an exact configured base/trusted Origin; without Origin, only Fetch Metadata same-origin or none is accepted. Denial is a localized RFC 9457 request.untrusted_origin (403), with no handler side effects. Request Host/Forwarded headers never establish trust. Independently authenticated Channex webhook/cron and private-code setup retain their own checks. ADR 0020 documents the boundaries and browser/database regression. The origin policy is also shared with the standalone team gateway; Better Auth keeps its additional CSRF checks.

Shared account mode scopes booking drain/recovery/acknowledgement and signals to workspace mappings. An authenticated unrelated global webhook records a signal without waking a shared drain. See Shared accounts in ADRs 0028/0034.

Guest identity operations share the existing transaction-bound reservation composition. The guest workflow covers duplicate review, manual merge, independent consent histories and ownership correction. The composed PostgreSQL HTTP regression checks that reservation write responses and reloads agree on the surviving identity, repointed reservations receive a new ETag and reject stale edits, and absorbed identities refuse consent/dismissal/undo writes with the final survivor pointer.

Property business dates, automatic and manual night audits are described in the workflow contract and ADR 0043. The night audit keeps the existing check-in charge timing; local-time scheduling, configured automatic no-shows and fee-free expired options share the same durable day-close path.

Channex composition shares ownsChannexWrites in http/channex.ts across booking import, ARI, Messages and worker safety jobs. It checks production/write enablement before reading the current installation marker on every call. channexVault in http/integration-secrets.ts wires the instance and current/legacy keyrings for connections, bookings and ARI; Messages retains its larger record bound through channexMessageVault.

http/privacy.ts composes guest data rights (ADR 0052) for the API and the worker: the country packs that http/country-packs.ts checks once for both runtimes, the Messages content and maintenance photo modules, the booking-import vault that opens archived revisions (bound to their workspace and revision identifier; one it cannot open is exported as null), and privacy/export-archive.ts, which writes an export’s ZIP of guest-data.json and a CRLF guest-data.csv with node:zlib. Jobs are queued in the caller’s transaction, and a committed export request wakes the request pump. The worker schedules privacy.dispatch every hour, and a hosted drain queues it at its start; it claims each due workspace’s retention pass alone, and privacy.retention and privacy.export run only while ownsBackgroundWrites holds. Messages takes its default content period from the workspace’s retention settings.

The HTTP runtime composes API key administration (digests from auth) and webhook endpoint administration (http/webhooks.ts); the principal resolver and the account’s workspaces resolve credentials through auth.authenticate, which spends the budgets of keys, and the resolver audits a key sent to another workspace or refused by an operation. http/integration-secrets.ts seals Channex values and webhook signing secrets under separate labels. The worker schedules webhooks.dispatch every minute and runs webhooks.deliver per workspace, both only while ownsBackgroundWrites holds (not a preview, and the database’s instance record is this instance’s), through http/webhook-sender.ts: node:https with agents of its own (no proxy from the environment), Standard Webhooks signatures, no redirects, a 10-second deadline that destroys the request, and http/destinations.ts, which refuses non-public addresses at registration and every resolved address when the socket connects. credentials:reencrypt also rewrites endpoint signing secrets. GUESTCRAFT_WEBHOOKS_ALLOW_PRIVATE=true (development only) allows private destinations and http:. See API keys and webhooks.

http/oauth.ts composes OAuth applications: the consent page’s runtime methods (oauthConsent, completeOAuthConsent), which verify the signed authorization request and the browser session, resolve the member’s principal in the chosen workspace, store the grant through core/integrations and complete Better Auth’s consent with it; and the discovery documents at the public origin, /.well-known/oauth-authorization-server/api/auth and /.well-known/oauth-protected-resource/api/v1|mcp, readable from any origin with a five-minute cache. GUESTCRAFT_OAUTH_OPEN_REGISTRATION (default true) lets applications register without signing in. The worker runs oauth.cleanup, a bounded pass of the applications’ lifetime rules, every hour and at each hosted drain. See OAuth applications.

Night audit settings, configured no-shows and expired tentative options share the durable property-date audit workflow. Cash drawer shifts provide currency-aware opening float, server-linked cash activity and versioned counted closure. See the night audit contract and cash shift contract for public operations, permissions, recovery, persistence and the Vercel daily-trigger cadence limit.

The HTTP runtime wires the browser-only tenancy gateway and the cross-organization workspace directory into API 6.8.0 and the shell. Operational workspace selection resolves live membership for the requested workspace; archiving excludes it from operational principals while keeping the organization/workspace administrative routes available for recovery.

pnpm --filter @guestcraft/server instance-admin list|grant usr_ID|revoke usr_ID is a protected maintenance command using owner credentials and the private setup code on stdin. It checks the installation identity and refuses to revoke the final administrator. See the recovery procedure. The role gives no organization data access.

With GUESTCRAFT_FILE_STORAGE set to s3 or vercel_blob, the web and the worker give db’s file store an adapter of @guestcraft/storage; the worker schedules files.collect_orphans every ten minutes (and a hosted drain enqueues it), which lists one page of the instance’s prefix of the store at most once an hour and deletes objects without a row after 24 hours. pnpm --filter @guestcraft/server files:move -- --to object|database moves the stored bytes with owner credentials, in resumable batches, and prints how many files it moved. See object storage.