@guestcraft/channex
GuestCraft’s AGPL adapters from the portable Channex client to the ports of
@guestcraft/core/channex. server composes these factories with credentials and
ownership checks. Core owns permissions, transactions, recovery and publication
policy; this package owns bounded HTTP calls and safe boundary conversion
(ADRs 0002 and
0022).
Exported factories
Section titled “Exported factories”Every port call takes a ChannexKey: the API key and the Channex environment that
verified it (staging for a sandbox workspace, production otherwise; ADR 0050).
All factories build their clients through channexClient (src/client.ts), which sends the
key to its own environment’s base URL and nowhere else. Factories accept an optional injected
fetch; simulator calls require that transport injection, and no factory exposes a base-URL
override. Message attachment links are kept only on the key’s own host.
| Factory | Port responsibilities |
|---|---|
channexRemote(fetch?) |
Validate a key (channex.key_refused with its environment for a 401, 403 or 404), list property summaries, scan required properties; no partial complete-list result |
bookingImportRemote(fetch?) |
Revision feed/history/current reads, a property’s revision history 100 per page, room discovery, acknowledgements and property booking-webhook registration/removal |
ariRemote(fetch?) |
Settings/plan discovery, absolute availability/restriction writes, task outcomes, a property’s recent tasks, read-back of current values and safe pacing/warning metadata |
messageRemote(fetch?) |
Messages installation, property feed, request events, conversations, sends, attachments, no-reply status and webhook registration/cleanup |
cardTokenizationRemote(fetch?) |
Core’s CardTokenizationRemote (ADR 0054): the Stripe payment method of an OTA booking’s card, the Stripe Tokenization App’s one documented operation (the operator installs the app in Channex’s interface) |
bookingReportRemote(fetch?) |
One Booking Reporting call per report (no-show, invalid card, cancel due to invalid card): sent, a definite refusal (not_supported, invalid_arguments, booking_not_found, access_refused), or ChannexUpstreamError with retry facts; an unreadable success, a 408 or 5xx, or a lost answer is an unknown outcome |
channexRemote uses five-second attempts and one GET retry, with a 15-second validation
deadline and 20-second property discovery/scan deadlines. bookingImportRemote uses
four-second attempts without retries, except acknowledgements and 100-revision history
pages allow ten seconds.
messageRemote uses five-second attempts and one GET retry with its wait capped at one second;
cardTokenizationRemote uses five-second attempts and never retries its write. ariRemote uses four-second reads without retries and payload-size-based
write timeouts of 6–16 seconds. bookingReportRemote uses one ten-second attempt per report,
never retried.
Connection validation reads one property and retains only the total. Complete account property discovery is bounded to 50 pages of 100. Shared-account summary discovery reads individually selected properties. The domain fences ownership before revision reads, acknowledgements and webhook deletion; see ADR 0028’s shared-account rules for recovery scope.
Boundary behavior
Section titled “Boundary behavior”Booking normalization handles offset-less provider timestamps and passes the OTA guest
language through raw; core canonicalizes it during projection. It passes payment_collect,
payment_type and the client’s reduced guarantee (card type, last four digits, expiry,
virtual flag and virtual-card details) to core; the card number, security code, holder’s name
and token never reach this package. Rejected revisions contain
safe identity metadata only; unreadable entries retain at most an outer ID. Core decides
durable quarantine, projection and acknowledgement. Rejected upstream bodies and card data
are discarded. Optional customer data may be absent or null; projection sanitization
belongs to core.
Connection failures return safe domain results. Booking operations translate client
failures to ChannexUpstreamError; local normalization runs outside that translation.
For client failures, connection, booking and ARI ports omit provider error bodies and
transport causes. cardTokenizationRemote returns only a failure kind: forbidden (403: the app is not
installed on the booking’s property, or not allowed), not_found, rejected (another refusal
Channex explained), unavailable (nothing happened) or unknown (a payment method whose answer
was lost, timed out, was a 408/5xx or unusable may have been made). Messages translates client failures to MessageRemoteError with a
reason, retryability and unknown-outcome flags, but retains the entire ChannexError
as cause, including provider error fields and any transport cause. ARI reads turn a
client failure into ChannexUpstreamError with the client’s retry facts (a refused key or
an invalid response is not retryable; a 429/5xx keeps Retry-After), let local bugs pass
unchanged, and report an unsupported property currency as channex.currency_unsupported
(ADR 0008, ADR 0034). ARI writes return pacing and outcome information;
a lost or malformed write response cannot be reported as confirmed synchronization.
ARI read-back turns core’s half-open nights into Channex’s inclusive date range and
answers one value per target and night. A restriction’s stop_sell is Channex’s
stop_sell_manual, the explicit closure, because Channex’s stop_sell also reports zero
availability or rate; fields Channex does not return are left out, and a property the key
cannot reach reads as no values. Channex documents one read-back rate per plan and night, also
for a per-person plan whose writes carry per-occupancy rates; core treats it as the main
option’s rate, an unverified assumption. Plan discovery returns each plan’s rate mode, occupancy
options (main option, and whether Channex derives the option’s rate) and child and infant fees as
decimal strings; an integer fee is read as minor units of the plan’s currency (unverified).
Settings carry agePolicy, the ages of the property’s hotel policy (infantMaxAge,
childrenMaxAge, each nullable), or null when the property has none; a failed policy read fails
the settings read rather than guess.
Workflow boundaries and verification
Section titled “Workflow boundaries and verification”Import, mapping, recovery, unlink cleanup, outbound ARI and messaging depend on the composed core/db/server/API/UI workflow. These factories do not start workers or persist anything. Current supported behavior and remaining operational limits are documented in booking import, ARI publication and Messages. Simulator evidence is not provider certification or proof of live channel delivery.
pnpm --filter @guestcraft/channex testpnpm --filter @guestcraft/channex typecheckAdapter tests inject the independent simulator or a scripted transport; no account is
needed. Server integration suites under packages/server/test/integration cover the
composed Channex, booking-import and Messages paths with real PostgreSQL/PgBouncer.
Run those suites one at a time. Built-app Channex browser scripts and their setup are
documented in the web README.