Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 38s
booking-notify.sx walks the booking stream into ordered notifications by kind (booked/promoted/held/confirmed/released/cancelled/waitlisted). Promotion detected by folding the waitlist (a booking for a waitlisted actor is a promotion). id=occ-key/seq -> idempotent re-derivation, no double-ping. Connects ticketing to the delivery layer. 265/265 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
3.2 KiB
Plaintext
103 lines
3.2 KiB
Plaintext
;; lib/events/booking-notify.sx — derive lifecycle notifications from the
|
|
;; booking stream, for delivery via notify.sx.
|
|
;;
|
|
;; Walking the append-only booking stream yields one notification per state
|
|
;; change, in order, classified by kind:
|
|
;;
|
|
;; :booked a confirmed booking
|
|
;; :promoted a booking for an actor who was on the waitlist (auto-promote)
|
|
;; :held a provisional hold (pending payment)
|
|
;; :confirmed a held seat became confirmed (payment succeeded)
|
|
;; :released a held seat was released (payment failed/expired)
|
|
;; :cancelled a seat was given up
|
|
;; :waitlisted an actor joined the waitlist
|
|
;;
|
|
;; Promotion is detected by folding the waitlist as we walk: a :booking for an
|
|
;; actor currently on the waitlist is a promotion, not a fresh booking.
|
|
;;
|
|
;; Each notification's id is occ-key/seq (the stream seq is unique and stable),
|
|
;; so re-deriving and re-delivering is idempotent — the notify transport dedups
|
|
;; on this id and never double-pings.
|
|
|
|
(define
|
|
ev-bn-kind
|
|
(fn
|
|
(typ promoted?)
|
|
(cond
|
|
((= typ :hold) :held)
|
|
((= typ :booking) (if promoted? :promoted :booked))
|
|
((= typ :confirm) :confirmed)
|
|
((= typ :cancel) :cancelled)
|
|
((= typ :release) :released)
|
|
((= typ :waitlist) :waitlisted)
|
|
(else nil))))
|
|
|
|
(define
|
|
ev-bn-update-waiting
|
|
(fn
|
|
(typ actor waiting)
|
|
(cond
|
|
((= typ :waitlist)
|
|
(if
|
|
(ev-bk-member? actor waiting)
|
|
waiting
|
|
(ev-bk-append waiting actor)))
|
|
((= typ :unwaitlist) (ev-bk-remove waiting actor))
|
|
((= typ :booking) (ev-bk-remove waiting actor))
|
|
((= typ :hold) (ev-bk-remove waiting actor))
|
|
(else waiting))))
|
|
|
|
(define ev-bn-mk (fn (occ-key label actor kind seq) {:id (str occ-key "/" seq) :event label :kind kind :recipient actor :seq seq}))
|
|
|
|
(define
|
|
ev-bn-step
|
|
(fn
|
|
(occ-key label events waiting)
|
|
(if
|
|
(empty? events)
|
|
(list)
|
|
(let
|
|
((e (first events)))
|
|
(let
|
|
((typ (persist/event-type e))
|
|
(actor (get (persist/event-data e) :actor))
|
|
(seq (persist/event-seq e)))
|
|
(let
|
|
((promoted? (and (= typ :booking) (ev-bk-member? actor waiting))))
|
|
(let
|
|
((kind (ev-bn-kind typ promoted?))
|
|
(waiting2 (ev-bn-update-waiting typ actor waiting)))
|
|
(if
|
|
(nil? kind)
|
|
(ev-bn-step occ-key label (rest events) waiting2)
|
|
(cons
|
|
(ev-bn-mk occ-key label actor kind seq)
|
|
(ev-bn-step occ-key label (rest events) waiting2))))))))))
|
|
|
|
;; The ordered lifecycle notifications for an occurrence's bookings. `label` is
|
|
;; a human-facing event id carried on each notification.
|
|
(define
|
|
ev/booking-notifications
|
|
(fn
|
|
(b occ-key label)
|
|
(ev-bn-step
|
|
occ-key
|
|
label
|
|
(persist/read b (ev-booking-stream occ-key))
|
|
(list))))
|
|
|
|
;; Filter notifications to a single kind.
|
|
(define
|
|
ev/notify-of-kind
|
|
(fn (notifs kind) (filter (fn (n) (= (get n :kind) kind)) notifs)))
|
|
|
|
;; Project a notification to notify.sx's (id recipient body) wire shape.
|
|
(define
|
|
ev/booking-notify->msg
|
|
(fn
|
|
(n)
|
|
(list
|
|
(get n :id)
|
|
(get n :recipient)
|
|
(list :booking-event (get n :kind) (get n :event)))))
|