Class: LifecycleSweepers
Defined in: server/bookings/lifecycle-sweepers.ts:128
Consumer-scheduled lifecycle sweepers (§11).
Three sweepers share one CAS'd transition helper (via thin public bridge
methods on BookingService) and one SweepResult-style return shape:
approvalExpiry()—pending→expired, only pastpendingExpiresAt(the structural guard onApprovable.expireenforces this insideapplyTransition— the sweeper relies on it).completion()—confirmed→completedafterendsAt(the structural guard onCompletable.completeenforcesendsAt <= now).noShow(opts?)—confirmed→no_showpaststartsAt + grace; absence must be established by the consumer AttendancePort. Grace reads from the per-booking PINNED policy bag (noShowGraceMinutes) unless the caller'sopts.noShowGraceMinutesoverrides it.
All three are intentionally NOT baked-in crons. Wire them to your consumer's Trigger.dev / BullMQ / pg-cron jobs and call the relevant method on each tick.
Constructors
Constructor
new LifecycleSweepers(deps: LifecycleSweepersDeps): LifecycleSweepers;Defined in: server/bookings/lifecycle-sweepers.ts:129
Parameters
| Parameter | Type |
|---|---|
deps | LifecycleSweepersDeps |
Returns
LifecycleSweepers
Methods
approvalExpiry()
approvalExpiry(opts?: ApprovalExpirySweepOptions): Promise<ApprovalExpirySweepResult>;Defined in: server/bookings/lifecycle-sweepers.ts:137
Approval-expiry sweep: finds every pending booking whose
pendingExpiresAt <= now and expires it via BookingService.expireApproval
(version-CAS, structural guard gate). Each booking carries a deterministic
idempotency key so re-runs append no new events (§6.5).
Parameters
| Parameter | Type |
|---|---|
opts | ApprovalExpirySweepOptions |
Returns
Promise<ApprovalExpirySweepResult>
completion()
completion(): Promise<CompletionSweepResult>;Defined in: server/bookings/lifecycle-sweepers.ts:191
Completion sweep: finds every confirmed booking whose schedule
endsAt <= now and marks it completed. The structural guard on
Completable.complete gates this — a booking whose hydrated endsAt
hasn't passed yet will throw InvalidTransitionError and be skipped.
Returns
Promise<CompletionSweepResult>
noShow()
noShow(opts?: NoShowSweepOptions): Promise<NoShowSweepResult>;Defined in: server/bookings/lifecycle-sweepers.ts:245
No-show sweep: finds every confirmed booking whose schedule
startsAt + grace <= now and marks it no_show only when the consumer
AttendancePort positively reports absence. Without that port this fails.
Grace resolution order:
opts.noShowGraceMinutes— caller override (useful for tests/operators).- Per-booking pinned policy bag
noShowGraceMinutes— the spec default. - 0 minutes — if neither is set.
Bookings still inside the grace window are skipped entirely (the query
uses a conservative startsAt <= now cutoff; per-booking grace filtering
happens in the loop). This two-pass approach avoids loading policy bags
for bookings that obviously haven't started yet, while correctly respecting
per-booking grace windows without requiring a DB-side join.
Parameters
| Parameter | Type |
|---|---|
opts | NoShowSweepOptions |
Returns
Promise<NoShowSweepResult>
offerExpiry()
offerExpiry(opts?: OfferExpirySweepOptions): Promise<OfferExpirySweepResult>;Defined in: server/bookings/lifecycle-sweepers.ts:352
Offer-expiry sweep (§6.4/§6.10/§11): find every offered waitlist offer
whose offerExpiresAt <= now (hits the [offerStatus, offerExpiresAt]
index) and reclaim it. Each expiry drives WaitlistService.expireOfferParty
— the offer's whole PARTY moves offered→waitlisted (offer row → expired)
as a unit, then the freed capacity CASCADES to the next fitting party.
Version-CAS'd + graceful-skip, exactly like the other sweepers: a
concurrent accept that won the CAS first leaves this expiry throwing
StaleBookingError (or InvalidTransitionError when the in-tx hydrate
runs after the winner committed) — both counted skipped. Each expiry
carries a deterministic idempotency key so a re-run appends nothing new.
De-duplicates by booking so a party with several expired offers isn't expired twice in one pass (the party-unit expiry already terminalizes all siblings).
NOT a baked-in cron — wire it to your consumer's scheduler like the others.
Parameters
| Parameter | Type |
|---|---|
opts | OfferExpirySweepOptions |
Returns
Promise<OfferExpirySweepResult>