;; 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)))))