Class: WaitlistService
Defined in: server/bookings/waitlist-service.ts:101
BookingService + the §7.1 transfer family (transfer / reschedule /
transferMany). Shipped as a subclass so the spec's "BookingService gains
transfer(...)" surface (§11) holds — the factory exposes ONE service that
is-a BookingService — while the transfer machinery lives in its own module.
Extends
Constructors
Constructor
new WaitlistService(deps: BookingFeatureDeps): WaitlistService;Defined in: server/bookings/booking-service.ts:135
Parameters
| Parameter | Type |
|---|---|
deps | BookingFeatureDeps |
Returns
WaitlistService
Inherited from
Properties
deps
protected readonly deps: BookingFeatureDeps;Defined in: server/bookings/booking-service.ts:135
Inherited from
Methods
acceptOffer()
acceptOffer(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:773
The offered party owning bookingId accepts (§6.10) — the whole party
batch-confirms all-or-none. A party of one is the batch of one. Delegates
to the batch-confirm primitive; capacity was already earmarked by the
offered state, so the self-excluding re-check passes.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
acceptOfferParty()
acceptOfferParty(bookingIds: readonly string[], opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>;Defined in: server/bookings/waitlist-service.ts:920
Batch accept for a whole party (§6.10) — all-or-none. Every member runs
through applyClaimTransitions (savepoint, capacity re-check with
self-exclusion, structural guard), and each member's active offer row flips
to confirmed + a waitlist_offer_accepted event is appended, all in one
transaction.
APPROVAL ROUTING (§7): a requiresApproval type must not reach confirmed
without a reviewer, and the waitlist is a second road there — one whose
offer is sent automatically by the cascade, with no human in the loop. So
for those types the party accepts into pending (acceptOfferForReview)
and waits for the normal approve/reject edges; approval_requested is
appended alongside the acceptance. Capacity is unaffected either way —
offered and pending both block (§6.6) — so the party holds its slot
while it waits, and applyClaimTransitions stamps pendingExpiresAt from
the pinned pendingTtlSeconds like any other entry into pending.
The whole party takes ONE road: if ANY member's pinned policy requires approval the entire party is routed for review. A party is a unit (§6.10), and erring toward review can only ever add scrutiny, never skip it.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingIds | readonly string[] | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>
applyClaimTransitions()
protected applyClaimTransitions(
members: readonly ClaimMember[],
event: string,
input: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}
): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>;Defined in: server/bookings/booking-service.ts:709
The batch CLAIM core (§6.6/§6.10) — every capacity-acquiring transition runs through here; a single claim is the batch of one.
One repos.transaction (15s budget) wrapping ONE explicit savepoint, so
all-or-none survives a consumer's ambient outer transaction (§6.6
joined-tx contract). Inside:
- hydrate + policy + STRUCTURAL evaluation for EVERY member — no writes;
- the §6.6 batch capacity check over the union overlap set (real row
locks, sorted-id order) — or, under
allowOverbook, skip it and append oneoverbooked_by_adminevent per schedule-bearing member; - only then apply every member's transition: version-CAS + timeline +
is_blockingmaintenance + outbox rows.
Any member failure throws before/instead of step 3 and rolls back to the savepoint — none commit. A batch of one unwraps to the inner error.
Parameters
| Parameter | Type | Description |
|---|---|---|
members | readonly ClaimMember[] | - |
event | string | - |
input | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
input.actorId | string | - |
input.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
input.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
input.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
input.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
input.expectedVersion? | number | - |
input.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>
Inherited from
TransferService.applyClaimTransitions
applyTransition()
protected applyTransition(
bookingId: string,
event: string,
opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
},
extras?: TransitionExtras
): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:597
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
event | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
extras? | TransitionExtras | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
TransferService.applyTransition
approve()
approve(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:385
pending→confirmed — deliberately NON-claiming, unlike confirm-from-held.
Capacity was acquired at submit (the claim path), and pending blocks
UNCONDITIONALLY in the §6.6 truth table — it carries no query-side
expiry predicate, so its occupancy can never silently lapse between
submit and approve. (pending→expired is a sweeper-driven version-CAS
transition; racing it makes this approve throw StaleBookingError rather
than double-book.) Contrast held, whose occupancy evaporates the
moment holdExpiresAt passes: confirming an expired-but-unswept hold
RE-ACQUIRES capacity, so confirm must re-check under the overlap-set
lock. Approve acquires nothing — and the claim path's capacity check
excludes a member's own booking rows anyway (§6.6 self-exclusion), so
routing approve through that capacity check would add no protection.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
assertPolicyAllows()
protected assertPolicyAllows(
booking: ComposedBookingRow,
event: string,
opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}
): Promise<void>;Defined in: server/bookings/booking-service.ts:955
POLICY seam (§8.1 step a / §8.3): approval routing from the PINNED
policy bag (§6.2) + domain: "bookings" rules evaluation — throws
PolicyDeniedError on deny. Protected so a consumer subclass can
extend/replace the policy layer.
Parameters
| Parameter | Type | Description |
|---|---|---|
booking | ComposedBookingRow | - |
event | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<void>
Inherited from
TransferService.assertPolicyAllows
cancel()
cancel(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
} & CascadeOptions): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:117
Cancel (§7) that also drives the capacity-frees CASCADE (§6.10): a
confirmed/offered/held booking releasing its slot frees capacity, so after
the cancel commits, offer that freed capacity to the next fitting waitlist
party. The base cancel (release + refund_due) is unchanged; this
override only adds the follow-on cascade on the released resource.
The cascade reads the POST-cancel blocking SUM (cancel flipped
is_blocking=false in the same tx, §6.6), so the just-freed unit correctly
reads as free — the "freed capacity must read free or the waitlist
deadlocks" regression.
Cancelling a booking that is itself waiting (waitlisted/offered) also
terminalizes ITS active offer row — see below.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } & CascadeOptions |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Overrides
cascadeOffers()
cascadeOffers(resourceId: string, opts?: CascadeOptions): Promise<void>;Defined in: server/bookings/waitlist-service.ts:522
The capacity-frees CASCADE (§6.10). Under the resource's overlap-set lock,
walk the FIFO party queue head-first and offer each party whose whole
demand fits the currently-free capacity — strict FIFO (a non-fitting head
blocks the queue). Offering flips a party's offers to offered (blocking),
so free capacity is recomputed as we go. A party is never split.
Call this whenever capacity frees on a resource (a confirmed/offered
booking cancels or expires); the primitive is idempotent — an offered
party is no longer queued, so a re-run never double-offers.
BOUNDED WORK: at most maxOffersPerCascade parties per call (default
DEFAULT_MAX_OFFERS_PER_CASCADE). This runs inline on
cancel/reject/decline/expiry and every pass re-reads the queued list, so an
uncapped loop would let one transition do work proportional to queue length
× parties offered. Hitting the cap is not lossy — the remainder keeps its
FIFO position for the next trigger.
FAIRNESS TRADE-OFF: each pass offers at most one party in its own transaction, then releases the lock and re-reads. A concurrent claim can interleave between parties, so a party is only ever offered capacity that was free at the head of ITS pass — the queue is never re-ordered, but a slot a non-fitting head just declined isn't held across passes.
Parameters
| Parameter | Type |
|---|---|
resourceId | string |
opts | CascadeOptions |
Returns
Promise<void>
complete()
complete(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:401
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
completeBooking()
completeBooking(bookingId: string, opts: {
actorId: string | null;
expectedVersion?: number;
idempotencyKey?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:456
System-actor confirmed→completed transition used by the completion
sweeper (§11). Structural guard (endsAt <= now) must pass; the host is
hydrated inside applyTransition from the schedule join.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; expectedVersion?: number; idempotencyKey?: string; } |
opts.actorId | string | null |
opts.expectedVersion? | number |
opts.idempotencyKey? | string |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
TransferService.completeBooking
confirm()
confirm(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:293
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
confirmMany()
confirmMany(bookingIds: readonly string[], opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>;Defined in: server/bookings/booking-service.ts:302
Batch confirm (§6.10): one transaction, one savepoint, all-or-none.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingIds | readonly string[] | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>
Inherited from
create()
create(raw: {
bookingTypeId: string;
capacityUnits?: number;
organizationId: string;
partyEmail?: string;
partyName?: string;
partyRef?: string;
payload?: Record<string, unknown>;
schedule?: {
endsAt: unknown;
resourceId: string;
startsAt: unknown;
timeZone: string;
};
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:155
Create a draft booking, pinning the type's published version (§6.2) and
appending booking_created + terms_pinned timeline rows — one tx.
When schedule is provided, a BookingSchedules row is written at
isBlocking: false — a draft never blocks capacity (spec §6.6 truth table).
Parameters
| Parameter | Type |
|---|---|
raw | { bookingTypeId: string; capacityUnits?: number; organizationId: string; partyEmail?: string; partyName?: string; partyRef?: string; payload?: Record<string, unknown>; schedule?: { endsAt: unknown; resourceId: string; startsAt: unknown; timeZone: string; }; } |
raw.bookingTypeId | string |
raw.capacityUnits? | number |
raw.organizationId | string |
raw.partyEmail? | string |
raw.partyName? | string |
raw.partyRef? | string |
raw.payload? | Record<string, unknown> |
raw.schedule? | { endsAt: unknown; resourceId: string; startsAt: unknown; timeZone: string; } |
raw.schedule.endsAt | unknown |
raw.schedule.resourceId | string |
raw.schedule.startsAt | unknown |
raw.schedule.timeZone | string |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
decideRun()
decideRun(resourceId: string, opts: {
actorId: string | null;
bookingTypeVersionId: string;
from: Date;
to: Date;
}): Promise<{
cancelled: number;
enrolled: number;
ran: boolean;
}>;Defined in: server/bookings/waitlist-service.ts:676
Run-decision (§6.10 / Phase 2): decide whether a program offering runs at
its enrollment deadline. Reads the pinned minEnrollment policy knob; if
the confirmed enrollment (SUM(capacity_units) of confirmed bookings on
the offering's window) is BELOW it, the class doesn't make — so every
still-active booking on that window (confirmed/offered/waitlisted/held/
pending) is auto-cancelled through the machine (each a real cancel
transition that releases capacity + records a run_cancelled reason).
No minEnrollment (or enrollment ≥ the minimum) ⇒ the offering runs and
nothing is cancelled. Idempotent by construction: once cancelled the
bookings leave the active set, so a re-run finds nothing to cancel.
CONCURRENCY: the enrollment read, the decision, and the teardown run under
ONE transaction holding the resource's overlap-set lock (the same lock the
claim/accept path takes). Without it a concurrent acceptOffer lifting the
class to quorum after the enrollment snapshot would still be torn down.
OFFER TEARDOWN: each cancelled booking's active offer is terminalized in the
same tx — super.cancel has no offer mutate hook, so a bare cancel would
leave an orphan row the expiry sweeper loops on. No re-queue: it's terminal.
Parameters
| Parameter | Type |
|---|---|
resourceId | string |
opts | { actorId: string | null; bookingTypeVersionId: string; from: Date; to: Date; } |
opts.actorId | string | null |
opts.bookingTypeVersionId | string |
opts.from | Date |
opts.to | Date |
Returns
Promise<{
cancelled: number;
enrolled: number;
ran: boolean;
}>
{ ran: boolean; enrolled: number; cancelled: number }.
declineOffer()
declineOffer(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
} & CascadeOptions): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:216
Public decline (§7). Terminalizes the whole PARTY's active offers
(declined, booking → waitlisted) as a unit, then cascades the freed
capacity to the next fitting party (§6.10).
The declining member's OWN outcome is propagated, not assumed: if its
decline did not apply — the offer is no longer active because a concurrent
acceptOffer won the version-CAS first (terminalizeOfferParty swallows
the Stale/InvalidTransition for best-effort party unity) — this throws
InvalidTransitionError rather than reporting the now-confirmed booking
as a "successful decline".
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } & CascadeOptions |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
declineOfferBooking()
declineOfferBooking(bookingId: string, opts: {
actorId: string | null;
eventType?: "waitlist_offer_declined" | "waitlist_offer_expired";
expectedVersion?: number;
idempotencyKey?: string;
terminalStatus?: "expired" | "declined";
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:989
offered→waitlisted — the system/consumer decline/expire (§6.4/§7). The
booking's machine edge is offered --declineOffer|expireOffer--> waitlisted
(active-WAITING, NOT terminal), so the member must remain on the waitlist.
This terminalizes the current offer row (declined/expired) in the same
tx as the status flip and appends waitlist_offer_declined/_expired, but
DOES NOT re-mint the member's queued row here — the caller
(WaitlistService) re-queues the party AFTER cascading the freed capacity,
so the just-declined party never re-grabs the slot it just declined (the
without livelock; see requeueParty).
This is the per-booking primitive both the public declineOffer and the
offer-expiry sweeper reuse. On a version-CAS loss / illegal edge it throws
StaleBookingError/InvalidTransitionError (the party terminalizer skips
such members best-effort; declineOffer propagates the head's outcome).
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; eventType?: "waitlist_offer_declined" | "waitlist_offer_expired"; expectedVersion?: number; idempotencyKey?: string; terminalStatus?: "expired" | "declined"; } |
opts.actorId | string | null |
opts.eventType? | "waitlist_offer_declined" | "waitlist_offer_expired" |
opts.expectedVersion? | number |
opts.idempotencyKey? | string |
opts.terminalStatus? | "expired" | "declined" |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
expireApproval()
expireApproval(bookingId: string, opts: {
actorId: string | null;
expectedVersion?: number;
idempotencyKey?: string;
} & CascadeOptions): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:186
Approval-TTL expiry (§11) that also drives the capacity-frees CASCADE.
The pending→expired sweep releases capacity for the same reason reject
does — see above. Driven by LifecycleSweepers.approvalExpiry().
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; expectedVersion?: number; idempotencyKey?: string; } & CascadeOptions |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Overrides
TransferService.expireApproval
expireHold()
expireHold(bookingId: string, opts: {
actorId: string | null;
expectedVersion?: number;
idempotencyKey?: string;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:523
System-actor held→expired transition used by the hold sweeper (§6.3).
Uses the same version-CAS path as every other transition — the sweeper
CAS races a concurrent confirm fairly: whichever commits first wins;
the loser throws StaleBookingError and is skipped (§6.6 / §14).
actorId is null for the hold sweeper (system actor). expectedVersion
is the version hydrated by the sweeper to participate in the CAS race.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; expectedVersion?: number; idempotencyKey?: string; reason?: string; } |
opts.actorId | string | null |
opts.expectedVersion? | number |
opts.idempotencyKey? | string |
opts.reason? | string |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
expireOfferParty()
expireOfferParty(bookingId: string, opts: {
actorId: string | null;
expectedVersion?: number;
offerExpiresAt: Date | null;
resourceId: string | null;
} & CascadeOptions): Promise<void>;Defined in: server/bookings/waitlist-service.ts:253
The offer-expiry sweeper arm's per-offer action (§11): terminalize the
expired offer's whole PARTY (expired, booking → waitlisted) as a unit,
then cascade the freed capacity to the next fitting party (§6.10). Each
member carries a deterministic idempotency key so a sweeper re-run appends
nothing new. Called by LifecycleSweepers.offerExpiry().
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; expectedVersion?: number; offerExpiresAt: Date | null; resourceId: string | null; } & CascadeOptions |
Returns
Promise<void>
generateUid()
protected generateUid(repos: BookingRepositories, organizationId: string): Promise<string>;Defined in: server/bookings/booking-service.ts:996
8-char external short code, retried on org-uniqueness collision.
Parameters
| Parameter | Type |
|---|---|
repos | BookingRepositories |
organizationId | string |
Returns
Promise<string>
Inherited from
hold()
hold(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
holdTtlSeconds: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:260
draft→held: sets holdExpiresAt = now + ttl in the same CAS update.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; holdTtlSeconds: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.holdTtlSeconds | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
holdMany()
holdMany(bookingIds: readonly string[], opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
holdTtlSeconds: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>;Defined in: server/bookings/booking-service.ts:272
Batch hold (§6.10): one transaction, one savepoint, all-or-none. Every
member's structural + capacity check is evaluated BEFORE any transition
applies, and the whole group shares ONE holdExpiresAt stamp so the hold
sweeper can expire it as a unit.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingIds | readonly string[] | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; holdTtlSeconds: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.holdTtlSeconds | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>
Inherited from
joinWaitlist()
joinWaitlist(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:812
draft→waitlisted (§6.4/§7). Non-claiming: waitlisted does NOT block
capacity (§6.6 truth table), so this is a plain version-CAS transition —
it never contends for a slot. In the SAME transaction it creates the
booking's queued waitlist_offers row (the FIFO queue entry, keyed on
registeredAt) and appends a waitlist_joined timeline event.
The cascade that later OFFERS this booking a freed slot lives in
WaitlistService (waitlist-service.ts); joinWaitlist only enqueues.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
machineFor()
protected machineFor(vertical: string): ErasedMachine;Defined in: server/bookings/booking-service.ts:138
Checked vertical → composed-machine lookup; throws UnknownBookingTypeError.
Parameters
| Parameter | Type |
|---|---|
vertical | string |
Returns
ErasedMachine
Inherited from
markNoShow()
markNoShow(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:409
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
markNoShowBySystem()
markNoShowBySystem(bookingId: string, opts: {
actorId: string | null;
expectedVersion?: number;
idempotencyKey?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:487
System-actor confirmed→no_show transition used by the no-show sweeper
(§11). No structural guard beyond status check — the sweeper is
responsible for enforcing the grace window before calling this.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; expectedVersion?: number; idempotencyKey?: string; } |
opts.actorId | string | null |
opts.expectedVersion? | number |
opts.idempotencyKey? | string |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
TransferService.markNoShowBySystem
reject()
reject(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
} & CascadeOptions): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:163
Reject (§7) that also drives the capacity-frees CASCADE (§6.10).
pending BLOCKS capacity and rejected does not (§6.6), so a reviewer
turning a booking down releases a seat exactly like a cancel — and on a
requiresApproval type that seat may be one the waitlist itself just
handed out, since acceptOffer routes those parties into pending for
review. Without this the next party is never offered and the freed seat
sits idle until some unrelated transition happens to cascade.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } & CascadeOptions |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Overrides
reschedule()
reschedule(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
endsAt?: Date;
expectedVersion?: number;
reason?: string;
resourceId?: string;
startsAt?: Date;
timeZone?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/transfer-service.ts:186
Reschedule convenience wrapper (§7.1/§11): a slot-only transfer. The
source carries a rescheduled (not transferred) event.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; endsAt?: Date; expectedVersion?: number; reason?: string; resourceId?: string; startsAt?: Date; timeZone?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.endsAt? | Date | - |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
opts.resourceId? | string | - |
opts.startsAt? | Date | - |
opts.timeZone? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
sendWaitlistOffer()
sendWaitlistOffer(bookingId: string, opts: {
actorId: string | null;
expectedVersion?: number;
idempotencyKey?: string;
offerExpiresAt: Date | null;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/waitlist-service.ts:862
waitlisted→offered — the system-driven offer send used by the capacity-
frees cascade (§6.10). Flips the booking's active queued offer row to
offered (stamping offeredAt + the offerExpiresAt accept-by deadline)
in the same tx as the status flip, and enqueues an offer_send_due outbox
row the dispatcher fans out through NotificationPort. offered BLOCKS
capacity (§6.6 truth table decision #2), earmarking the freed slot so the
party's later acceptOffer cannot lose a race.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | { actorId: string | null; expectedVersion?: number; idempotencyKey?: string; offerExpiresAt: Date | null; } |
opts.actorId | string | null |
opts.expectedVersion? | number |
opts.idempotencyKey? | string |
opts.offerExpiresAt | Date | null |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
submit()
submit(bookingId: string, opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/booking-service.ts:358
draft→pending — a CLAIMING transition (§6.6 truth table: pending
BLOCKS capacity), so it runs the claim path: overlap-set lock + batch
capacity check + window containment. On the old non-claiming path two
concurrent submits could both reach pending and double-book the slot.
Parameters
| Parameter | Type | Description |
|---|---|---|
bookingId | string | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
toHost()
protected toHost(b: ComposedBookingRow): ErasedBookingHost;Defined in: server/bookings/booking-service.ts:981
Map the hydrated row to the composed host the machine guards read.
startsAt/endsAt/timeZone/resourceId are sourced from the joined
schedule row (§5.2 hydration spec). When no schedule exists (draft with
no schedule), endsAt is undefined and the completable guard (endsAt <= now) correctly fails, so complete is unreachable — by design.
offerExpiresAt is surfaced from the ACTIVE waitlist offer row (§6.4) so
the §5.2 offered→confirmed structural guard reads the real accept-by
deadline off the host — not undefined from a bare row (a hydration bug).
offers is filtered to active rows at hydration; the single offered/
queued row's deadline (or null when none) is what the guard sees.
Protected so the transfer family (§7.1) reuses the same hydration.
Parameters
| Parameter | Type |
|---|---|
b | ComposedBookingRow |
Returns
ErasedBookingHost
Inherited from
transfer()
transfer(bookingId: string, opts: Omit<{
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
to: {
bookingTypeId?: string;
capacityUnits?: number;
groupId?: string | null;
partyEmail?: string | null;
partyName?: string | null;
partyRef?: string | null;
schedule?: {
endsAt?: Date;
resourceId?: string;
startsAt?: Date;
timeZone?: string;
};
};
}, "to"> & {
to: {
bookingTypeId?: string;
capacityUnits?: number;
groupId?: string | null;
partyEmail?: string | null;
partyName?: string | null;
partyRef?: string | null;
schedule?: {
endsAt?: Date;
resourceId?: string;
startsAt?: Date;
timeZone?: string;
};
};
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>;Defined in: server/bookings/transfer-service.ts:167
Transfer one booking along the to delta (§7.1) — returns the successor
(or the same booking for an in-place repoint). The batch of one.
Parameters
| Parameter | Type |
|---|---|
bookingId | string |
opts | Omit<{ actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; to: { bookingTypeId?: string; capacityUnits?: number; groupId?: string | null; partyEmail?: string | null; partyName?: string | null; partyRef?: string | null; schedule?: { endsAt?: Date; resourceId?: string; startsAt?: Date; timeZone?: string; }; }; }, "to"> & { to: { bookingTypeId?: string; capacityUnits?: number; groupId?: string | null; partyEmail?: string | null; partyName?: string | null; partyRef?: string | null; schedule?: { endsAt?: Date; resourceId?: string; startsAt?: Date; timeZone?: string; }; }; } |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}>
Inherited from
transferMany()
transferMany(members: readonly TransferMember[], opts: {
actorId: string;
allowLateCancel?: boolean;
allowOutsideHours?: boolean;
allowOverbook?: boolean;
allowTransfer?: boolean;
expectedVersion?: number;
reason?: string;
}): Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>;Defined in: server/bookings/transfer-service.ts:212
Move a batch of bookings atomically (§7.1): all-or-none, savepoint-
wrapped, capacity-checked over the UNION overlap set with
excludeBookingIds = the sources — the wedding (hall + kitchen +
parking) moves whole or not at all. Returns the successors (or the same
booking for in-place members) in member order.
Parameters
| Parameter | Type | Description |
|---|---|---|
members | readonly TransferMember[] | - |
opts | { actorId: string; allowLateCancel?: boolean; allowOutsideHours?: boolean; allowOverbook?: boolean; allowTransfer?: boolean; expectedVersion?: number; reason?: string; } | - |
opts.actorId | string | - |
opts.allowLateCancel? | boolean | Admin late-cancel override (spec §7): when true, cancel skips the rules-engine cancellation policy guard and records a cancelled_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook (§6.6). The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than cancel. |
opts.allowOutsideHours? | boolean | Admin outside-hours override (spec §6.9): when true, claiming transitions skip the window-containment check and record an hours_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowOverbook. The CONSUMER owns the permission decision. |
opts.allowOverbook? | boolean | Admin overbook override (spec §6.6): when true, claiming transitions skip the capacity/overlap check and record an overbooked_by_admin timeline event instead. The CONSUMER owns the permission decision — taproot is auth-agnostic and only honors + records the flag. |
opts.allowTransfer? | boolean | Admin transfer override (spec §7.1): when true, the transfer/reschedule family skips the rules-engine policy guard for the transfer event and records a transfer_overridden_by_admin timeline event instead — the same bypass-and-record contract as allowLateCancel/allowOverbook. The CONSUMER owns the permission decision; taproot only honors + records the flag. Ignored on every event other than transfer. |
opts.expectedVersion? | number | - |
opts.reason? | string | - |
Returns
Promise<{
amountOwedCents: number | null;
bookingTypeId: string;
bookingTypeVersionId: string;
capacityUnits: number;
createdAt: Date;
createdBy: string | null;
currency: string | null;
deletedAt: Date | null;
deletedBy: string | null;
groupId: string | null;
holdExpiresAt: Date | null;
id: string;
organizationId: string;
origin: BookingOrigin;
paid: boolean;
partyEmail: string | null;
partyName: string | null;
partyRef: string | null;
payload: JsonValue;
paymentRef: string | null;
pendingExpiresAt: Date | null;
status: BookingStatus;
statusUpdatedAt: Date;
statusUpdatedBy: string | null;
supersededById: string | null;
uid: string;
updatedAt: Date;
updatedBy: string | null;
version: number;
}[]>