Kaizen
Browse modulesBookingsbookings/serverClasses

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:

  1. approvalExpiry() — pending→expired, only past pendingExpiresAt (the structural guard on Approvable.expire enforces this inside applyTransition — the sweeper relies on it).
  2. completion() — confirmed→completed after endsAt (the structural guard on Completable.complete enforces endsAt <= now).
  3. noShow(opts?) — confirmed→no_show past startsAt + grace; absence must be established by the consumer AttendancePort. Grace reads from the per-booking PINNED policy bag (noShowGraceMinutes) unless the caller's opts.noShowGraceMinutes overrides 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

ParameterType
depsLifecycleSweepersDeps

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

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:

  1. opts.noShowGraceMinutes — caller override (useful for tests/operators).
  2. Per-booking pinned policy bag noShowGraceMinutes — the spec default.
  3. 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

ParameterType
optsNoShowSweepOptions

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

ParameterType
optsOfferExpirySweepOptions

Returns

Promise<OfferExpirySweepResult>

On this page