@fanfare-io/fanfare-sdk-core 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import*as t from"valibot";import{isFanfareError as o,createError as e}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{parseResponse as i}from"../core/parse-response.js";import{createPollScheduler as s}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as r}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as a}from"../state/events.js";function l(t,o,e){const n=i(t,o,{endpoint:e},{throwOnFailure:!0});if(!n.ok)throw n.error;return n.value}const d=t.object({id:t.string(),startTime:t.string(),endTime:t.string(),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),timezone:t.nullish(t.string()),capacity:t.number(),available:t.number()}),c=t.object({slots:t.array(d)}),m=t.object({id:t.string(),type:t.literal("appointment"),openAt:t.nullish(t.string()),closeAt:t.nullish(t.string()),slotDurationMinutes:t.nullish(t.number()),timeZone:t.nullish(t.string()),locationId:t.nullish(t.string()),totalSlots:t.nullish(t.number()),availableSlots:t.nullish(t.number())}),p=t.string(),h=t.picklist(["consumer","admin","killed"]),u=t.object({bookingId:t.string(),appointmentId:t.string(),consumerId:t.string(),slotId:t.string(),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),startTime:t.string(),endTime:t.string(),confirmationCode:t.string(),status:p,cancelledCause:t.nullish(h),createdAt:t.string()}),g=t.object({booking:t.nullish(t.object({bookingId:t.string(),appointmentId:t.string(),consumerId:t.string(),slotId:t.nullish(t.string()),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),startTime:t.nullish(t.string()),endTime:t.nullish(t.string()),confirmationCode:t.nullish(t.string()),status:p,cancelledCause:t.nullish(h)}))}),I=t.object({booking:u,bookingId:t.string(),previousSlotId:t.string(),newSlotId:t.string(),confirmationCode:t.string(),status:t.string(),cancelledCause:t.nullish(h)});function b(t){const{appointmentId:o,status:e}=t;let i;return"booked"===e||"completed"===e||"cancelled"===e?i=e:(n().warn("Unknown appointment consumerStatus; treating as booked",{appointmentId:o,status:e}),i="booked"),{bookingId:t.bookingId,appointmentId:t.appointmentId,slotId:t.slotId,locationId:t.locationId??void 0,locationName:t.locationName??void 0,startTime:t.startTime,endTime:t.endTime,confirmationCode:t.confirmationCode,consumerStatus:i,cancelledCause:t.cancelledCause??void 0}}function k(t){return{id:t.id,startTime:t.startTime,endTime:t.endTime,locationId:t.locationId??void 0,locationName:t.locationName??void 0,timezone:t.timezone??void 0,capacity:t.capacity,available:t.available}}class w{constructor(t,o){this.logger=n(),this.monitorRuntime=new r,this.pollSchedulers=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.observedBooking=/* @__PURE__ */new Map,this.destroyed=!1,this.http=t,this.pollingIntervalMs=o?.pollingIntervalMs??3e4}async get(t,n){try{const o=await this.http.get(`/appointments/${t}`,n),e=l(m,o,`/appointments/${t}`);return{id:e.id,type:"appointment",openAt:e.openAt??void 0,closeAt:e.closeAt??void 0,slotDurationMinutes:e.slotDurationMinutes??void 0,bookingWindowOpenAt:e.openAt??void 0,bookingWindowCloseAt:e.closeAt??void 0,timezone:e.timeZone??void 0,locationId:e.locationId??void 0,totalSlots:e.totalSlots??void 0,availableSlots:e.availableSlots??void 0}}catch(i){if(this.logger.error("Failed to get appointment",{appointmentId:t,error:i}),o(i))throw i;throw e.networkError("Failed to get appointment",{error:i})}}async getSlots(t,n,i){try{const o=new URLSearchParams({startDate:n.startDate,endDate:n.endDate}),e=await this.http.get(`/appointments/${t}/slots?${o.toString()}`,i);return l(c,e,`/appointments/${t}/slots`).slots.map(k)}catch(s){if(this.logger.error("Failed to get appointment slots",{appointmentId:t,error:s}),o(s))throw s;throw e.networkError("Failed to get appointment slots",{error:s})}}async getMe(t){try{const o=await this.http.get(`/appointments/${t}/me`),e=l(g,o,`/appointments/${t}/me`);return e.booking?function(t){const{appointmentId:o,status:e}=t;let i;return"booked"===e||"completed"===e||"cancelled"===e?i=e:(n().warn("Unknown appointment consumerStatus; treating as booked",{appointmentId:o,status:e}),i="booked"),{bookingId:t.bookingId,appointmentId:t.appointmentId,slotId:t.slotId??"",locationId:t.locationId??void 0,locationName:t.locationName??void 0,startTime:t.startTime??"",endTime:t.endTime??"",confirmationCode:t.confirmationCode??void 0,consumerStatus:i,cancelledCause:t.cancelledCause??void 0}}(e.booking):null}catch(i){if(this.logger.error("Failed to get current booking",{appointmentId:t,error:i}),o(i))throw i;throw e.networkError("Failed to get current booking",{error:i})}}async book(t,n,i,s,r){try{const o=await this.http.post(`/appointments/${t}/book`,{slotId:n,locationId:i??null},s),e=b(l(u,o,`/appointments/${t}/book`));return a().emit("experience:distribution-entered",{distributionType:"appointment",distributionId:t,...r}),e}catch(d){if(this.logger.error("Failed to book appointment",{appointmentId:t,slotId:n,locationId:i,error:d}),o(d))throw d;throw e.networkError("Failed to book appointment",{error:d})}}async cancel(t,n,i,s){try{await this.http.post(`/appointments/${t}/cancel`,{slotId:n,locationId:i??null},s)}catch(r){if(this.logger.error("Failed to cancel appointment",{appointmentId:t,slotId:n,locationId:i,error:r}),o(r))throw r;throw e.networkError("Failed to cancel appointment",{error:r})}}async reschedule(t,n,i,s){try{const o=await this.http.post(`/appointments/${t}/reschedule`,{newSlotId:n,newLocationId:i??null},s),e=b(l(I,o,`/appointments/${t}/reschedule`).booking);if(this.isMonitoring(t)){const o=this.monitorRuntime.getDisplayAtom(t);o&&o.set({type:"appointment",booking:e,msUntilStart:Math.max(0,Date.parse(e.startTime)-Date.now())}),this.startMonitor(t,o?{displayAtom:o}:void 0,void 0,this.pollSchedulers.get(t)?.onDegradedChange,e)}return e}catch(r){if(this.logger.error("Failed to reschedule appointment",{appointmentId:t,newSlotId:n,newLocationId:i,error:r}),o(r))throw r;throw e.networkError("Failed to reschedule appointment",{error:r})}}startMonitoring(t,o,e,n){this.startMonitor(t,o,e,n)}startMonitor(t,o,e,n,i){if(this.destroyed)return;this.monitorRuntime.start(t,o,e),this.stopPolling(t);const r=this.monitorRuntime.getDisplayAtom(t)?.get();this.observedBooking.set(t,"appointment"===r?.type&&void 0!==r.booking);const a=(this.monitorGenerations.get(t)??0)+1;this.monitorGenerations.set(t,a);const l=s({baseMs:this.pollingIntervalMs,suppressBackoff:()=>!1,onDegradedChange:n,shouldContinue:()=>!this.destroyed&&this.monitorGenerations.get(t)===a&&this.pollSchedulers.has(t),run:async()=>{if(!this.destroyed&&this.monitorGenerations.get(t)===a)try{const o=i??await this.getMe(t);if(i=void 0,this.destroyed||this.monitorGenerations.get(t)!==a)return;const e=this.monitorRuntime.getDisplayAtom(t);if(!o)return void(this.observedBooking.get(t)&&(this.monitorRuntime.notify(t,{type:"ended",outcome:{type:"closed"}}),this.stopMonitoring(t)));if(this.observedBooking.set(t,!0),"cancelled"===o.consumerStatus||"completed"===o.consumerStatus)return this.monitorRuntime.notify(t,{type:"ended",outcome:{type:o.consumerStatus,..."cancelled"===o.consumerStatus&&"killed"===o.cancelledCause?{reason:"killed"}:{}}}),void this.stopMonitoring(t);const n=Math.max(0,Date.parse(o.startTime)-Date.now());if(!e)return;e.set({type:"appointment",booking:{slotId:o.slotId,locationId:o.locationId,locationName:o.locationName,startTime:o.startTime,endTime:o.endTime,confirmationCode:o.confirmationCode,consumerStatus:o.consumerStatus,cancelledCause:o.cancelledCause},msUntilStart:n})}catch(o){throw this.logger.error("Appointment polling tick failed",{id:t,error:o}),o}}});this.pollSchedulers.set(t,{scheduler:l,onDegradedChange:n}),l.start()}stopMonitoring(t){this.stopPolling(t),this.monitorRuntime.stop(t),this.observedBooking.delete(t),this.monitorGenerations.set(t,(this.monitorGenerations.get(t)??0)+1)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}bumpMonitorGeneration(t){this.monitorGenerations.set(t,(this.monitorGenerations.get(t)??0)+1)}destroy(){this.destroyed=!0,this.monitorGenerations.clear(),this.observedBooking.clear();for(const t of Array.from(this.pollSchedulers.keys()))this.stopPolling(t);this.monitorRuntime.clear()}stopPolling(t){const o=this.pollSchedulers.get(t);void 0!==o&&(o.scheduler.stop(),this.pollSchedulers.delete(t))}}export{w as AppointmentManagementModule};
1
+ import*as t from"valibot";import{isFanfareError as o,createError as e}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{parseResponse as i}from"../core/parse-response.js";import{createPollScheduler as r}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as s}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as a}from"../state/events.js";function l(t,o,e){const n=i(t,o,{endpoint:e},{throwOnFailure:!0});if(!n.ok)throw n.error;return n.value}const d=t.object({id:t.string(),startTime:t.string(),endTime:t.string(),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),timezone:t.nullish(t.string()),capacity:t.number(),available:t.number()}),c=t.object({slots:t.array(d)}),m=t.object({id:t.string(),type:t.literal("appointment"),openAt:t.nullish(t.string()),closeAt:t.nullish(t.string()),slotDurationMinutes:t.nullish(t.number()),timeZone:t.nullish(t.string()),locationId:t.nullish(t.string()),totalSlots:t.nullish(t.number()),availableSlots:t.nullish(t.number())}),p=t.string(),h=t.picklist(["consumer","admin","killed"]),u=t.object({bookingId:t.string(),appointmentId:t.string(),consumerId:t.string(),slotId:t.string(),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),startTime:t.string(),endTime:t.string(),confirmationCode:t.string(),status:p,cancelledCause:t.nullish(h),createdAt:t.string()}),g=t.object({booking:t.nullish(t.object({bookingId:t.string(),appointmentId:t.string(),consumerId:t.string(),slotId:t.nullish(t.string()),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),startTime:t.nullish(t.string()),endTime:t.nullish(t.string()),confirmationCode:t.nullish(t.string()),status:p,cancelledCause:t.nullish(h)}))}),I=t.object({booking:u,bookingId:t.string(),previousSlotId:t.string(),newSlotId:t.string(),confirmationCode:t.string(),status:t.string(),cancelledCause:t.nullish(h)});function b(t){const{appointmentId:o,status:e}=t;let i;return"booked"===e||"completed"===e||"cancelled"===e?i=e:(n().warn("Unknown appointment consumerStatus; treating as booked",{appointmentId:o}),i="booked"),{bookingId:t.bookingId,appointmentId:t.appointmentId,slotId:t.slotId,locationId:t.locationId??void 0,locationName:t.locationName??void 0,startTime:t.startTime,endTime:t.endTime,confirmationCode:t.confirmationCode,consumerStatus:i,cancelledCause:t.cancelledCause??void 0}}function k(t){return{id:t.id,startTime:t.startTime,endTime:t.endTime,locationId:t.locationId??void 0,locationName:t.locationName??void 0,timezone:t.timezone??void 0,capacity:t.capacity,available:t.available}}class w{constructor(t,o){this.logger=n(),this.monitorRuntime=new s,this.pollSchedulers=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.observedBooking=/* @__PURE__ */new Map,this.destroyed=!1,this.http=t,this.pollingIntervalMs=o?.pollingIntervalMs??3e4}async get(t,n){try{const o=await this.http.get(`/appointments/${t}`,n),e=l(m,o,`/appointments/${t}`);return{id:e.id,type:"appointment",openAt:e.openAt??void 0,closeAt:e.closeAt??void 0,slotDurationMinutes:e.slotDurationMinutes??void 0,bookingWindowOpenAt:e.openAt??void 0,bookingWindowCloseAt:e.closeAt??void 0,timezone:e.timeZone??void 0,locationId:e.locationId??void 0,totalSlots:e.totalSlots??void 0,availableSlots:e.availableSlots??void 0}}catch(i){if(this.logger.error("Failed to get appointment",{appointmentId:t,error:i}),o(i))throw i;throw e.networkError("Failed to get appointment",{error:i})}}async getSlots(t,n,i){try{const o=new URLSearchParams({startDate:n.startDate,endDate:n.endDate}),e=await this.http.get(`/appointments/${t}/slots?${o.toString()}`,i);return l(c,e,`/appointments/${t}/slots`).slots.map(k)}catch(r){if(this.logger.error("Failed to get appointment slots",{appointmentId:t,error:r}),o(r))throw r;throw e.networkError("Failed to get appointment slots",{error:r})}}async getMe(t){try{const o=await this.http.get(`/appointments/${t}/me`),e=l(g,o,`/appointments/${t}/me`);return e.booking?function(t){const{appointmentId:o,status:e}=t;let i;return"booked"===e||"completed"===e||"cancelled"===e?i=e:(n().warn("Unknown appointment consumerStatus; treating as booked",{appointmentId:o}),i="booked"),{bookingId:t.bookingId,appointmentId:t.appointmentId,slotId:t.slotId??"",locationId:t.locationId??void 0,locationName:t.locationName??void 0,startTime:t.startTime??"",endTime:t.endTime??"",confirmationCode:t.confirmationCode??void 0,consumerStatus:i,cancelledCause:t.cancelledCause??void 0}}(e.booking):null}catch(i){if(this.logger.error("Failed to get current booking",{appointmentId:t,error:i}),o(i))throw i;throw e.networkError("Failed to get current booking",{error:i})}}async book(t,n,i,r,s){try{const o=await this.http.post(`/appointments/${t}/book`,{slotId:n,locationId:i??null},r),e=b(l(u,o,`/appointments/${t}/book`));return a().emit("experience:distribution-entered",{distributionType:"appointment",distributionId:t,...s}),e}catch(d){if(this.logger.error("Failed to book appointment",{appointmentId:t,slotId:n,locationId:i,error:d}),o(d))throw d;throw e.networkError("Failed to book appointment",{error:d})}}async cancel(t,n,i,r){try{await this.http.post(`/appointments/${t}/cancel`,{slotId:n,locationId:i??null},r)}catch(s){if(this.logger.error("Failed to cancel appointment",{appointmentId:t,slotId:n,locationId:i,error:s}),o(s))throw s;throw e.networkError("Failed to cancel appointment",{error:s})}}async reschedule(t,n,i,r){try{const o=await this.http.post(`/appointments/${t}/reschedule`,{newSlotId:n,newLocationId:i??null},r),e=b(l(I,o,`/appointments/${t}/reschedule`).booking);if(this.isMonitoring(t)){const o=this.monitorRuntime.getDisplayAtom(t);o&&o.set({type:"appointment",booking:e,msUntilStart:Math.max(0,Date.parse(e.startTime)-Date.now())}),this.startMonitor(t,o?{displayAtom:o}:void 0,void 0,this.pollSchedulers.get(t)?.onDegradedChange,e)}return e}catch(s){if(this.logger.error("Failed to reschedule appointment",{appointmentId:t,newSlotId:n,newLocationId:i,error:s}),o(s))throw s;throw e.networkError("Failed to reschedule appointment",{error:s})}}startMonitoring(t,o,e,n){this.startMonitor(t,o,e,n)}startMonitor(t,o,e,n,i){if(this.destroyed)return;this.monitorRuntime.start(t,o,e),this.stopPolling(t);const s=this.monitorRuntime.getDisplayAtom(t)?.get();this.observedBooking.set(t,"appointment"===s?.type&&void 0!==s.booking);const a=(this.monitorGenerations.get(t)??0)+1;this.monitorGenerations.set(t,a);const l=r({baseMs:this.pollingIntervalMs,suppressBackoff:()=>!1,onDegradedChange:n,shouldContinue:()=>!this.destroyed&&this.monitorGenerations.get(t)===a&&this.pollSchedulers.has(t),run:async()=>{if(!this.destroyed&&this.monitorGenerations.get(t)===a)try{const o=i??await this.getMe(t);if(i=void 0,this.destroyed||this.monitorGenerations.get(t)!==a)return;const e=this.monitorRuntime.getDisplayAtom(t);if(!o)return void(this.observedBooking.get(t)&&(this.monitorRuntime.notify(t,{type:"ended",outcome:{type:"closed"}}),this.stopMonitoring(t)));if(this.observedBooking.set(t,!0),"cancelled"===o.consumerStatus||"completed"===o.consumerStatus)return this.monitorRuntime.notify(t,{type:"ended",outcome:{type:o.consumerStatus,..."cancelled"===o.consumerStatus&&"killed"===o.cancelledCause?{reason:"killed"}:{}}}),void this.stopMonitoring(t);const n=Math.max(0,Date.parse(o.startTime)-Date.now());if(!e)return;e.set({type:"appointment",booking:{slotId:o.slotId,locationId:o.locationId,locationName:o.locationName,startTime:o.startTime,endTime:o.endTime,confirmationCode:o.confirmationCode,consumerStatus:o.consumerStatus,cancelledCause:o.cancelledCause},msUntilStart:n})}catch(o){throw this.logger.error("Appointment polling tick failed",{id:t,error:o}),o}}});this.pollSchedulers.set(t,{scheduler:l,onDegradedChange:n}),l.start()}stopMonitoring(t){this.stopPolling(t),this.monitorRuntime.stop(t),this.observedBooking.delete(t),this.monitorGenerations.set(t,(this.monitorGenerations.get(t)??0)+1)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}bumpMonitorGeneration(t){this.monitorGenerations.set(t,(this.monitorGenerations.get(t)??0)+1)}destroy(){this.destroyed=!0,this.monitorGenerations.clear(),this.observedBooking.clear();for(const t of Array.from(this.pollSchedulers.keys()))this.stopPolling(t);this.monitorRuntime.clear()}stopPolling(t){const o=this.pollSchedulers.get(t);void 0!==o&&(o.scheduler.stop(),this.pollSchedulers.delete(t))}}export{w as AppointmentManagementModule};
@@ -1 +1 @@
1
- import{AuctionDistributionResults as t}from"@fanfare-io/fanfare-sdk-contracts";import{createError as i}from"../core/errors.js";import{buildGrantPaymentAffordance as e}from"../experiences/sequence-driver.js";import{isGrantPendingWin as n}from"./auction-status.js";import{isUnresolvedAuctionConsumer as s}from"./auction.sequence.js";function o(t){return!t||("lost"===t.type||"closed"===t.type)&&!t.reason}function a(t){return new u(t)}function r(e){const{distribution:n,consumer:a,grant:r,settlement:c,selection:u,outcome:d}=e;if(!n)return{phase:"unavailable"};const m=function(t){if("auction"!==t.type)throw i.validationError("Auction driver received a non-auction distribution");return t}(n),h=a&&"auction"===a.mechanism?function(t){const{mechanism:i,...e}=t;return e}(a):l(n);if(r)return"completed"===d?.type?p(m,h,d):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===m.lifecycle?{phase:"enterable",mechanism:"auction",distribution:m,consumer:l(m)}:p(m,h,{type:"expired"}):c&&u?{phase:"granted",mechanism:"auction",distribution:m,consumer:h,grant:r,settlement:c,selection:u}:{phase:"unavailable"};if("closed"===m.lifecycle){if("not_bid"===h.status&&m.result===t.FLOOR_UNSOLD&&o(d))return p(m,h,{type:"lost",reason:t.FLOOR_UNSOLD});if("auction"===a?.mechanism&&!d)return p(m,h,{type:"lost"})}const b=d??function(t){return"lost"===t.status?{type:"lost"}:void 0}(h);return b?p(m,h,b):"settling"===m.lifecycle?{phase:"settling",mechanism:"auction",distribution:m,consumer:h}:s(h)?{phase:"participating",mechanism:"auction",distribution:m,consumer:h}:"scheduled"===m.lifecycle?{phase:"scheduled",mechanism:"auction",distribution:m}:"open"===m.lifecycle?{phase:"enterable",mechanism:"auction",distribution:m,consumer:h}:p(m,h,{type:"closed"})}function c(t,i,e){return t&&"auction"===t.mechanism&&i?"won"===i.type?{...t,status:"won"}:!e?.finalize&&"not_bid"===t.status&&o(i)?t:{...t,status:"lost"}:t}class u{constructor(t){this.deps=t,this.mechanism="auction"}project(t){return r(t)}buildView(t,n){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const s=function(t){if(!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("Auction driver received a non-auction sequence");return t}(t);switch(s.phase){case"scheduled":return{phase:"scheduled",mechanism:"auction",distribution:s.distribution,startsAt:s.distribution.startsAt??s.distribution.opensAt};case"enterable":return{phase:"enterable",mechanism:"auction",distribution:s.distribution,consumer:s.consumer,display$:n.displayAtom,loadSelectionOptions:n.loadSelectionOptions,bid:t=>this.bid(t)};case"participating":return{phase:"participating",mechanism:"auction",consumer:s.consumer,display$:n.displayAtom,loadSelectionOptions:n.loadSelectionOptions,bid:t=>this.bid(t)};case"settling":return{phase:"settling",mechanism:"auction",distribution:s.distribution,consumer:s.consumer};case"granted":{const t={phase:"granted",mechanism:"auction",distribution:s.distribution,consumer:s.consumer,grant:s.grant},{settlement:i}=s,o=s.selection;if("awaiting_assignment"===o.status)return{...t,settlement:i,selection:o};if("required"===o.status)return{...t,settlement:i,selection:o,select:n.select,loadSelectionOptions:n.loadSelectionOptions};if("internal"===i.processor&&"at_end"===i.mode){const a=e(i,s.pendingAction,n);return a?{...t,settlement:i,selection:o,loadSelectionOptions:n.loadSelectionOptions,select:n.select,claim:()=>n.claim(),payment:a}:{...t,settlement:i,selection:o,loadSelectionOptions:n.loadSelectionOptions,select:n.select,claim:()=>n.claim()}}return"external"===i.processor?{...t,settlement:i,selection:o,loadSelectionOptions:n.loadSelectionOptions,select:n.select,claim:()=>n.claim()}:{phase:"unavailable",reason:"checkout_misconfigured"}}case"ended":return{phase:"ended",mechanism:"auction",consumer:s.consumer,outcome:s.outcome}}}startMonitoring(t){this.activeMonitorId=t.participation.id,this.deps.auctions.startMonitoring(t.participation.id,{displayAtom:t.displayAtom},t.onUpdate,t.onDegradedChange)}stopMonitoring(){this.activeMonitorId&&(this.deps.auctions.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async bid(t){await this.deps.runSerializedOperation(async()=>{const i=this.currentAuctionSequenceForBid(),e=i.consumer.distributionId??i.consumer.id??i.distribution.id,s=i.consumer.selection,o="auction"===i.distribution.details?.type?i.distribution.details:void 0;d(t,o?.auctionType);const a=await this.deps.auctions.bid(e,t),c=this.deps.getCurrentSequence(),u=c&&("enterable"===c.phase||"participating"===c.phase||"settling"===c.phase)&&"mechanism"in c&&"auction"===c.mechanism&&c.distribution.id===e;if("won"===a.status||"lost"===a.status)return void(u&&("won"===a.status&&a.admissionGrant?this.deps.applyMonitorUpdate({type:"granted",token:a.admissionGrant}):"lost"===a.status?this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"lost"}}):n(a)||this.deps.applyMonitorUpdate({type:"resync"})));if(!u)return;const l=function(t){switch(t){case"not_bid":case"winning":case"outbid":return t;case"won":case"lost":return}}(a.status);void 0!==l&&this.deps.commit(r({distribution:i.distribution,consumer:{..."participating"===i.phase?i.consumer:{},mechanism:"auction",status:l,id:e,distributionId:e,...s?{selection:s}:{},currentBid:a.amount,highestBid:a.highestBid}}))})}currentAuctionSequenceForBid(){const t=this.deps.getCurrentSequence();if(!t||"enterable"!==t.phase&&"participating"!==t.phase||!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("No auction is available to bid on");return t}}function d(t,e){if("dutch"!==e){if(void 0===t.amount)throw i.validationError("A bid amount is required for an English auction")}else if(void 0!==t.amount)throw i.validationError("A Dutch auction clinches at the current price; omit the bid amount")}function l(t){return{status:"not_bid",distributionId:t.id}}function p(t,i,e){return{phase:"ended",mechanism:"auction",distribution:t,consumer:i,outcome:{...e,distributionType:e.distributionType??t.type,distributionId:e.distributionId??t.id,at:e.at??/* @__PURE__ */(new Date).toISOString()}}}export{d as assertBidInputMatchesMechanism,a as createAuctionDriver,o as isUpgradeableGenericLoss,r as projectAuctionSequence,c as withAuctionTerminalConsumer};
1
+ import{AuctionDistributionResults as t}from"@fanfare-io/fanfare-sdk-contracts";import{createError as i}from"../core/errors.js";import{selectionModeFields as e,buildGrantPaymentAffordance as n}from"../experiences/sequence-driver.js";import{isGrantPendingWin as s}from"./auction-status.js";import{isUnresolvedAuctionConsumer as o}from"./auction.sequence.js";function a(t){return!t||("lost"===t.type||"closed"===t.type)&&!t.reason}function r(t){return new d(t)}function c(n){const s=function(e){const{distribution:n,consumer:s,grant:r,settlement:c,selection:u,outcome:d}=e;if(!n)return{phase:"unavailable"};const l=function(t){if("auction"!==t.type)throw i.validationError("Auction driver received a non-auction distribution");return t}(n),h=s&&"auction"===s.mechanism?function(t){const{mechanism:i,...e}=t;return e}(s):p(n);if(r)return"completed"===d?.type?m(l,h,d):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===l.lifecycle?{phase:"enterable",mechanism:"auction",distribution:l,consumer:p(l)}:m(l,h,{type:"expired"}):c&&u?{phase:"granted",mechanism:"auction",distribution:l,consumer:h,grant:r,settlement:c,selection:u}:{phase:"unavailable"};if("closed"===l.lifecycle){if("not_bid"===h.status&&l.result===t.FLOOR_UNSOLD&&a(d))return m(l,h,{type:"lost",reason:t.FLOOR_UNSOLD});if("auction"===s?.mechanism&&!d)return m(l,h,{type:"lost"})}const b=d??function(t){return"lost"===t.status?{type:"lost"}:void 0}(h);if(b)return m(l,h,b);if("settling"===l.lifecycle)return{phase:"settling",mechanism:"auction",distribution:l,consumer:h};if(o(h))return{phase:"participating",mechanism:"auction",distribution:l,consumer:h};if("scheduled"===l.lifecycle)return{phase:"scheduled",mechanism:"auction",distribution:l};if("open"===l.lifecycle)return{phase:"enterable",mechanism:"auction",distribution:l,consumer:h};return m(l,h,{type:"closed"})}(n);return"unavailable"===s.phase?s:{...s,...e(n)}}function u(t,i,e){return t&&"auction"===t.mechanism&&i?"won"===i.type?{...t,status:"won"}:!e?.finalize&&"not_bid"===t.status&&a(i)?t:{...t,status:"lost"}:t}class d{constructor(t){this.deps=t,this.mechanism="auction"}project(t){return c(t)}buildView(t,s){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const o=function(t){if(!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("Auction driver received a non-auction sequence");return t}(t);switch(o.phase){case"scheduled":return{phase:"scheduled",mechanism:"auction",...e(o),distribution:o.distribution,startsAt:o.distribution.startsAt??o.distribution.opensAt};case"enterable":return{phase:"enterable",mechanism:"auction",...e(o),distribution:o.distribution,consumer:o.consumer,display$:s.displayAtom,loadSelectionOptions:s.loadSelectionOptions,bid:t=>this.bid(t)};case"participating":return{phase:"participating",mechanism:"auction",...e(o),consumer:o.consumer,display$:s.displayAtom,loadSelectionOptions:s.loadSelectionOptions,bid:t=>this.bid(t)};case"settling":return{phase:"settling",mechanism:"auction",...e(o),distribution:o.distribution,consumer:o.consumer};case"granted":{const t={phase:"granted",mechanism:"auction",...e(o),distribution:o.distribution,consumer:o.consumer,grant:o.grant},{settlement:i}=o,a=o.selection;if("awaiting_assignment"===a.status)return{...t,settlement:i,selection:a};if("required"===a.status)return{...t,settlement:i,selection:a,select:s.select,loadSelectionOptions:s.loadSelectionOptions};if("internal"===i.processor&&"at_end"===i.mode){const e=n(i,o.pendingAction,s);return e?{...t,settlement:i,selection:a,loadSelectionOptions:s.loadSelectionOptions,select:s.select,claim:()=>s.claim(),payment:e}:{...t,settlement:i,selection:a,loadSelectionOptions:s.loadSelectionOptions,select:s.select,claim:()=>s.claim()}}return"external"===i.processor?{...t,settlement:i,selection:a,loadSelectionOptions:s.loadSelectionOptions,select:s.select,claim:()=>s.claim()}:{phase:"unavailable",reason:"checkout_misconfigured"}}case"ended":return{phase:"ended",mechanism:"auction",...e(o),consumer:o.consumer,outcome:o.outcome}}}startMonitoring(t){this.activeMonitorId=t.participation.id,this.deps.auctions.startMonitoring(t.participation.id,{displayAtom:t.displayAtom},t.onUpdate,t.onDegradedChange)}stopMonitoring(){this.activeMonitorId&&(this.deps.auctions.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async bid(t){await this.deps.runSerializedOperation(async()=>{const i=this.currentAuctionSequenceForBid(),e=i.consumer.distributionId??i.consumer.id??i.distribution.id,n=i.consumer.selection,o="auction"===i.distribution.details?.type?i.distribution.details:void 0;l(t,o?.auctionType);const a=await this.deps.auctions.bid(e,t),r=this.deps.getCurrentSequence(),u=r&&("enterable"===r.phase||"participating"===r.phase||"settling"===r.phase)&&"mechanism"in r&&"auction"===r.mechanism&&r.distribution.id===e;if("won"===a.status||"lost"===a.status)return void(u&&("won"===a.status&&a.admissionGrant?this.deps.applyMonitorUpdate({type:"granted",token:a.admissionGrant}):"lost"===a.status?this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"lost"}}):s(a)||this.deps.applyMonitorUpdate({type:"resync"})));if(!u)return;const d=function(t){switch(t){case"not_bid":case"winning":case"outbid":return t;case"won":case"lost":return}}(a.status);void 0!==d&&this.deps.commit(c({distribution:i.distribution,consumer:{..."participating"===i.phase?i.consumer:{},mechanism:"auction",status:d,id:e,distributionId:e,...n?{selection:n}:{},currentBid:a.amount,highestBid:a.highestBid}}))})}currentAuctionSequenceForBid(){const t=this.deps.getCurrentSequence();if(!t||"enterable"!==t.phase&&"participating"!==t.phase||!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("No auction is available to bid on");return t}}function l(t,e){if("dutch"!==e){if(void 0===t.amount)throw i.validationError("A bid amount is required for an English auction")}else if(void 0!==t.amount)throw i.validationError("A Dutch auction clinches at the current price; omit the bid amount")}function p(t){return{status:"not_bid",distributionId:t.id}}function m(t,i,e){return{phase:"ended",mechanism:"auction",distribution:t,consumer:i,outcome:{...e,distributionType:e.distributionType??t.type,distributionId:e.distributionId??t.id,at:e.at??/* @__PURE__ */(new Date).toISOString()}}}export{l as assertBidInputMatchesMechanism,r as createAuctionDriver,a as isUpgradeableGenericLoss,c as projectAuctionSequence,u as withAuctionTerminalConsumer};
@@ -1,5 +1,5 @@
1
1
  import { AdmissionGrant, AuctionConsumerStatus, GrantedSettlement, SequenceOutcome } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
2
- import { GrantedSelection } from '@fanfare-io/fanfare-sdk-contracts/selection';
2
+ import { GrantedSelection, SelectionMode } from '@fanfare-io/fanfare-sdk-contracts/selection';
3
3
  import { ReadableAtom } from 'nanostores';
4
4
  import { AuctionDisplayState } from '../experiences/distribution-monitor.types';
5
5
  import { DistributionSummary } from '../experiences/journey.types';
@@ -52,6 +52,7 @@ export type AuctionSequence = AuctionScheduled | AuctionEnterable | AuctionParti
52
52
  export interface AuctionScheduled {
53
53
  /** Mechanism discriminator for auction narrowing. */
54
54
  mechanism: "auction";
55
+ selectionMode?: SelectionMode;
55
56
  /** Phase discriminator; scheduled auctions cannot be bid on yet. */
56
57
  phase: "scheduled";
57
58
  /** Auction distribution facts from the journey projector. */
@@ -65,6 +66,7 @@ export interface AuctionScheduled {
65
66
  export interface AuctionEnterable {
66
67
  /** Mechanism discriminator for auction narrowing. */
67
68
  mechanism: "auction";
69
+ selectionMode?: SelectionMode;
68
70
  /** Phase discriminator; `bid()` is legal on the matching view. */
69
71
  phase: "enterable";
70
72
  /** Open auction distribution facts from the journey projector. */
@@ -80,6 +82,7 @@ export interface AuctionEnterable {
80
82
  export interface AuctionParticipating {
81
83
  /** Mechanism discriminator for auction narrowing. */
82
84
  mechanism: "auction";
85
+ selectionMode?: SelectionMode;
83
86
  /** Phase discriminator; auction display and bidding are live. */
84
87
  phase: "participating";
85
88
  /** Auction distribution facts from the journey projector. */
@@ -95,6 +98,7 @@ export interface AuctionParticipating {
95
98
  export interface AuctionSettling {
96
99
  /** Mechanism discriminator for auction narrowing. */
97
100
  mechanism: "auction";
101
+ selectionMode?: SelectionMode;
98
102
  /** Phase discriminator; no consumer action is legal while settling. */
99
103
  phase: "settling";
100
104
  /** Settling auction distribution facts from the journey projector. */
@@ -110,6 +114,7 @@ export interface AuctionSettling {
110
114
  export interface AuctionGranted {
111
115
  /** Mechanism discriminator for auction narrowing. */
112
116
  mechanism: "auction";
117
+ selectionMode?: SelectionMode;
113
118
  /** Phase discriminator; `claim()` is legal on the matching view. */
114
119
  phase: "granted";
115
120
  /** Auction distribution facts from the journey projector. */
@@ -135,6 +140,7 @@ export interface AuctionGranted {
135
140
  export interface AuctionEnded {
136
141
  /** Mechanism discriminator for auction narrowing. */
137
142
  mechanism: "auction";
143
+ selectionMode?: SelectionMode;
138
144
  /** Phase discriminator; no auction actions are legal after this phase. */
139
145
  phase: "ended";
140
146
  /** Auction distribution facts from the journey projector. */
@@ -162,6 +168,7 @@ export interface AuctionScheduledView {
162
168
  phase: "scheduled";
163
169
  /** Mechanism discriminator for auction rendering. */
164
170
  mechanism: "auction";
171
+ selectionMode?: SelectionMode;
165
172
  /** Auction distribution facts from the journey projector. */
166
173
  distribution: DistributionSummary & {
167
174
  type: "auction";
@@ -177,6 +184,7 @@ export interface AuctionEnterableView {
177
184
  phase: "enterable";
178
185
  /** Mechanism discriminator for auction rendering. */
179
186
  mechanism: "auction";
187
+ selectionMode?: SelectionMode;
180
188
  /** Open auction distribution facts from the journey projector. */
181
189
  distribution: DistributionSummary & {
182
190
  type: "auction";
@@ -208,6 +216,7 @@ export interface AuctionParticipatingView {
208
216
  phase: "participating";
209
217
  /** Mechanism discriminator for auction rendering. */
210
218
  mechanism: "auction";
219
+ selectionMode?: SelectionMode;
211
220
  /** Consumer auction participation facts. */
212
221
  consumer: AuctionSequenceConsumerState;
213
222
  /** Live auction display facts: the price model, currency, and close time. */
@@ -234,6 +243,7 @@ export interface AuctionSettlingView {
234
243
  phase: "settling";
235
244
  /** Mechanism discriminator for auction rendering. */
236
245
  mechanism: "auction";
246
+ selectionMode?: SelectionMode;
237
247
  /** Settling auction distribution facts from the journey projector. */
238
248
  distribution: DistributionSummary & {
239
249
  type: "auction";
@@ -249,6 +259,7 @@ interface AuctionGrantedViewBase {
249
259
  phase: "granted";
250
260
  /** Mechanism discriminator for auction rendering. */
251
261
  mechanism: "auction";
262
+ selectionMode?: SelectionMode;
252
263
  /** Auction distribution facts from the journey projector. */
253
264
  distribution: DistributionSummary & {
254
265
  type: "auction";
@@ -317,6 +328,7 @@ export interface AuctionEndedView {
317
328
  phase: "ended";
318
329
  /** Mechanism discriminator for auction rendering. */
319
330
  mechanism: "auction";
331
+ selectionMode?: SelectionMode;
320
332
  /** Consumer auction facts at the terminal outcome. */
321
333
  consumer: AuctionSequenceConsumerState;
322
334
  /** Server-derived terminal outcome for the auction participation. */
@@ -328,6 +340,7 @@ export interface AuctionGrantedAwaitingAssignmentView {
328
340
  phase: "granted";
329
341
  /** Mechanism discriminator. */
330
342
  mechanism: "auction";
343
+ selectionMode?: SelectionMode;
331
344
  /** Distribution associated with this grant. */
332
345
  distribution: DistributionSummary & {
333
346
  type: "auction";
@@ -349,6 +362,7 @@ export interface AuctionGrantedSelectionRequiredView {
349
362
  phase: "granted";
350
363
  /** Mechanism discriminator. */
351
364
  mechanism: "auction";
365
+ selectionMode?: SelectionMode;
352
366
  /** Distribution associated with this grant. */
353
367
  distribution: DistributionSummary & {
354
368
  type: "auction";
@@ -56,6 +56,11 @@ export interface OtpRequest {
56
56
  phone?: string;
57
57
  defaultCountry?: string;
58
58
  name?: string;
59
+ /**
60
+ * The experience the consumer is signing in to. The sign-in email then carries that experience's
61
+ * branding, matching the rest of its emails; without it the email uses the organization's.
62
+ */
63
+ experienceId?: string;
59
64
  }
60
65
  export interface OtpVerify {
61
66
  email?: string;
@@ -1 +1 @@
1
- import{classify as r}from"./error-disposition.js";import{FanfareError as e,ErrorCodes as o}from"./errors.js";const t={[o.OTP_INVALID]:"auth.invalidCode",[o.OTP_EXPIRED]:"auth.codeExpired",[o.INVALID_SESSION]:"common.sessionExpired",[o.INVALID_REFRESH_TOKEN]:"common.sessionExpired",[o.SERVICE_UNAVAILABLE]:"error.overloadedGeneric",[o.TIMEOUT]:"error.overloadedGeneric",[o.NETWORK_ERROR]:"error.overloadedGeneric",[o.FEATURE_DISABLED]:"error.featureDisabled",[o.NO_ACCESS]:"widget.noAccess.message",[o.FORBIDDEN]:"error.forbidden",[o.DISTRIBUTION_NOT_OPEN]:"error.notOpen",[o.DISTRIBUTION_DEACTIVATED]:"error.deactivated",[o.ALREADY_BOOKED]:"appointment.error.alreadyBooked",[o.ACTIVE_BOOKING_EXISTS]:"appointment.error.activeBookingExists",[o.SLOT_NOT_AVAILABLE]:"appointment.error.slotNotAvailable",[o.SLOT_EXPIRED]:"appointment.error.slotExpired",[o.SLOT_OUT_OF_WINDOW]:"appointment.error.slotOutOfWindow",[o.CANNOT_CANCEL_ONGOING_OR_PAST]:"appointment.error.cannotCancel",[o.NEW_SLOT_FULL]:"appointment.error.newSlotFull",[o.BOOKING_COMPLETED]:"appointment.error.bookingCompleted",[o.NO_EXISTING_BOOKING]:"appointment.error.noExistingBooking",[o.INVALID_SLOT]:"appointment.error.invalidSlot"},i=/* @__PURE__ */new Set([o.DISTRIBUTION_DEACTIVATED,o.DISTRIBUTION_CLOSED,o.DISTRIBUTION_ORDER_LIMIT,o.SOLD_OUT,o.CHECKOUT_MISCONFIGURED]);function n(r){return"INLINE"===r||"STEP_RESET"===r?"error.inlineGeneric":"error.description"}function s(e,s){const{disposition:E}=r(e.code,e.status,e.action),I="number"==typeof e.retryAfter&&Number.isFinite(e.retryAfter)?e.retryAfter:void 0,d=function(r,e,i){return r===o.RATE_LIMITED?i?"error.rateLimited":"error.rateLimitedGeneric":r===o.OVERLOADED?i?"error.overloaded":"error.overloadedGeneric":t[r]??n(e)}(e.code,E,void 0!==I);let O=s(d,void 0!==I?{values:{seconds:I}}:void 0);O.includes("{{")&&(O=s(n(E)));const a="PANEL"===E?s("error.title"):void 0;return{code:e.code,title:a,description:O,disposition:E,retryAfter:e.retryAfter,support:e.support,allowRetry:!i.has(e.code)}}function E(r){if(r instanceof e)return r;const t=r instanceof Error?r.message:String(r);return new e(t,o.INTERNAL_ERROR)}function I(r){return{code:r.code,status:r.status,requestId:r.requestId,correlationId:r.correlationId,details:r.details,issues:r.issues}}export{E as ensureFanfareError,I as toDebug,s as toDisplay};
1
+ import{classify as e}from"./error-disposition.js";import{FanfareError as r,ErrorCodes as o}from"./errors.js";const t={[o.OTP_INVALID]:"auth.invalidCode",[o.OTP_EXPIRED]:"auth.codeExpired",[o.INVALID_SESSION]:"common.sessionExpired",[o.INVALID_REFRESH_TOKEN]:"common.sessionExpired",[o.SERVICE_UNAVAILABLE]:"error.overloadedGeneric",[o.TIMEOUT]:"error.overloadedGeneric",[o.NETWORK_ERROR]:"error.overloadedGeneric",[o.FEATURE_DISABLED]:"error.featureDisabled",[o.NO_ACCESS]:"widget.noAccess.message",[o.FORBIDDEN]:"error.forbidden",[o.DISTRIBUTION_NOT_OPEN]:"error.notOpen",[o.DISTRIBUTION_DEACTIVATED]:"error.deactivated",[o.ALREADY_BOOKED]:"appointment.error.alreadyBooked",[o.ACTIVE_BOOKING_EXISTS]:"appointment.error.activeBookingExists",[o.SLOT_NOT_AVAILABLE]:"appointment.error.slotNotAvailable",[o.SLOT_EXPIRED]:"appointment.error.slotExpired",[o.SLOT_OUT_OF_WINDOW]:"appointment.error.slotOutOfWindow",[o.CANNOT_CANCEL_ONGOING_OR_PAST]:"appointment.error.cannotCancel",[o.NEW_SLOT_FULL]:"appointment.error.newSlotFull",[o.BOOKING_COMPLETED]:"appointment.error.bookingCompleted",[o.NO_EXISTING_BOOKING]:"appointment.error.noExistingBooking",[o.INVALID_SLOT]:"appointment.error.invalidSlot",[o.SELECTION_REQUIRED]:"selection.error.required",[o.SELECTION_INVALID_CHOICE]:"selection.error.invalidChoice",[o.SELECTION_NOT_REQUIRED]:"selection.error.notRequired",[o.SELECTION_LOCKED]:"selection.locked.final",[o.SELECTION_MISMATCH]:"selection.error.mismatch",[o.SELECTION_UNAVAILABLE]:"selection.error.unavailable",[o.SELECTION_LIMIT_REACHED]:"selection.error.limitReached",[o.SELECTION_ASSIGNMENT_PENDING]:"selection.error.assignmentPending",[o.ADMISSION_SELECTION_UNRESOLVED]:"selection.error.admissionUnresolved",[o.SOLD_OUT]:"selection.error.soldOut",[o.STOCK_UNSEEDED]:"selection.error.unseeded"},i=/* @__PURE__ */new Set([o.DISTRIBUTION_DEACTIVATED,o.DISTRIBUTION_CLOSED,o.DISTRIBUTION_ORDER_LIMIT,o.SOLD_OUT,o.CHECKOUT_MISCONFIGURED]);function n(e){return"INLINE"===e||"STEP_RESET"===e?"error.inlineGeneric":"error.description"}function E(r,E){const{disposition:s}=e(r.code,r.status,r.action),I="number"==typeof r.retryAfter&&Number.isFinite(r.retryAfter)?r.retryAfter:void 0,O=function(e,r,i){return e===o.RATE_LIMITED?i?"error.rateLimited":"error.rateLimitedGeneric":e===o.OVERLOADED?i?"error.overloaded":"error.overloadedGeneric":t[e]??n(r)}(r.code,s,void 0!==I);let d=E(O,void 0!==I?{values:{seconds:I}}:void 0);d.includes("{{")&&(d=E(n(s)));const T="PANEL"===s?E("error.title"):void 0;return{code:r.code,title:T,description:d,disposition:s,retryAfter:r.retryAfter,support:r.support,allowRetry:!i.has(r.code)}}function s(e){if(e instanceof r)return e;const t=e instanceof Error?e.message:String(e);return new r(t,o.INTERNAL_ERROR)}function I(e){return{code:e.code,status:e.status,requestId:e.requestId,correlationId:e.correlationId,details:e.details,issues:e.issues}}export{s as ensureFanfareError,I as toDebug,E as toDisplay};
@@ -1 +1 @@
1
- import{createError as e}from"./errors.js";import{maskPII as t}from"./utils.js";class o{constructor(e){this.levels={debug:0,info:1,warn:2,error:3},this.config=e}shouldLog(e){return!!this.config.enabled&&this.levels[e]>=this.levels[this.config.level]}format(e,o,r){const i=[`${this.config.prefix?`[${this.config.prefix}] `:""}${/* @__PURE__ */(new Date).toISOString()} [${e.toUpperCase()}] ${o}`];if(void 0!==r){const e=t(r);i.push(e)}}debug(e,t){this.shouldLog("debug")&&this.format("debug",e,t)}info(e,t){this.shouldLog("info")&&this.format("info",e,t)}warn(e,t){this.shouldLog("warn")&&this.format("warn",e,t)}error(e,t){this.shouldLog("error")&&this.format("error",e,t)}setLevel(e){this.config.level=e}setEnabled(e){this.config.enabled=e}}let r=null;function i(e){return r&&!e||(r=new o(e||{enabled:!1,level:"warn",prefix:"Fanfare"})),r}function n(){if(!r)throw e.notInitialized("Logger not initialized. Call createLogger first.");return r}function s(){return r}export{i as createLogger,n as getLogger,s as peekLogger};
1
+ import{createError as e}from"./errors.js";import{maskPII as r}from"./utils.js";class o{constructor(e){this.levels={debug:0,info:1,warn:2,error:3},this.config=e}shouldLog(e){return!!this.config.enabled&&this.levels[e]>=this.levels[this.config.level]}format(e,o,i){const t=[`${this.config.prefix?`[${this.config.prefix}] `:""}${/* @__PURE__ */(new Date).toISOString()} [${e.toUpperCase()}] ${o}`];if(void 0!==i){const e=r(i);void 0!==e&&t.push(e)}switch(e){case"debug":case"info":break;case"warn":console.warn(...t);break;case"error":console.error(...t)}}debug(e,r){this.shouldLog("debug")&&this.format("debug",e,r)}info(e,r){this.shouldLog("info")&&this.format("info",e,r)}warn(e,r){this.shouldLog("warn")&&this.format("warn",e,r)}error(e,r){this.shouldLog("error")&&this.format("error",e,r)}setLevel(e){this.config.level=e}setEnabled(e){this.config.enabled=e}}let i=null;function t(e){return i&&!e||(i=new o(e||{enabled:!1,level:"warn",prefix:"Fanfare"})),i}function n(){if(!i)throw e.notInitialized("Logger not initialized. Call createLogger first.");return i}function s(){return i}export{t as createLogger,n as getLogger,s as peekLogger};
@@ -1 +1 @@
1
- import*as e from"valibot";import{getEventBus as r}from"../state/events.js";import{ProtocolError as s}from"./errors.js";import{getLogger as o}from"./logger.js";function t(t,n,i,u={}){const a=e.safeParse(t,n);if(a.success)return{ok:!0,value:a.output};const d=function(e){const r=e[0];if(!r)return"no issues reported";const s=r.path?.map(e=>String(e.key)).join(".")??"",o=s?` at "${s}"`:"",t=e.length>1?` (and ${e.length-1} more)`:"";return`${r.message}${o}${t}`}(a.issues),p=`Response from ${i.endpoint} failed schema validation: ${d}`,m=new s(p,i.endpoint,a.issues,i.requestId);if(o().error("Protocol error: response failed schema validation",{endpoint:i.endpoint,requestId:i.requestId,issues:a.issues}),r().emit("journey:error",{kind:"protocolError",endpoint:i.endpoint,message:p,requestId:i.requestId,issues:a.issues}),u.throwOnFailure)throw m;return{ok:!1,error:m}}export{t as parseResponse};
1
+ import*as e from"valibot";import{getEventBus as t}from"../state/events.js";import{ProtocolError as r}from"./errors.js";import{getLogger as o}from"./logger.js";function s(s,i,p,u={}){const d=e.safeParse(s,i);if(d.success)return{ok:!0,value:d.output};const a=function(e){const t=e[0];if(!t)return"no issues reported";const r=n(t),o=r?` at "${r}"`:"",s=e.length>1?` (and ${e.length-1} more)`:"";return`expected ${t.expected??t.type}${o}${s}`}(d.issues),c=`Response from ${p.endpoint} failed schema validation: ${a}`,m=new r(c,p.endpoint,d.issues,p.requestId);var f;if(o().error("Protocol error: response failed schema validation",{endpoint:p.endpoint,requestId:p.requestId,issues:(f=d.issues,f.map(e=>({path:n(e),expected:e.expected??e.type})))}),t().emit("journey:error",{kind:"protocolError",endpoint:p.endpoint,message:c,requestId:p.requestId,issues:d.issues}),u.throwOnFailure)throw m;return{ok:!1,error:m}}function n(e){const t=e.path?.map(e=>String(e.key))??[];return"never"===e.expected&&t.length>0&&(t[t.length-1]="<unexpected>"),t.join(".")}export{s as parseResponse};
@@ -46,11 +46,10 @@ export declare function isStorageAvailable(type: "localStorage" | "sessionStorag
46
46
  */
47
47
  export declare function safeJsonParse<T>(json: string, fallback?: T): T | null;
48
48
  /**
49
- * Mask email for privacy
50
- */
51
- export declare function maskEmail(email: string): string;
52
- /**
53
- * Mask sensitive PII data in objects
49
+ * Project arbitrary log data onto what is safe to print: errors reduced to their identity, and
50
+ * string, number and boolean values held under a safe key. A top-level primitive carries no key to
51
+ * judge it by and is dropped, as is every key outside the set — dropped rather than masked, so that
52
+ * neither the value nor its presence can be inferred from the console.
54
53
  */
55
54
  export declare function maskPII(data: unknown): unknown;
56
55
  /**
@@ -1 +1 @@
1
- import{v7 as t}from"uuid";function n(){return t()}const r=(t,n=.2,r=Math.random)=>Math.round(t*(1-n+2*r()*n));function e(t,n=/* @__PURE__ */new WeakSet){if(null===t||"object"!=typeof t)return t;if(n.has(t))return t;if(t instanceof Date)return new Date(t.getTime());if(t instanceof Array)return n.add(t),t.map(t=>e(t,n));if(t instanceof Object){n.add(t);const r={};for(const o in t)Object.prototype.hasOwnProperty.call(t,o)&&(r[o]=e(t[o],n));return r}return t}function o(){return"undefined"!=typeof window&&void 0!==window.document}function i(t){if(!o())return!1;try{const n=window[t],r="__storage_test__";return n.setItem(r,r),n.removeItem(r),!0}catch{return!1}}function u(t){const[n,r]=t.split("@");if(!n||!r)return t;const e=Math.min(2,n.length);return`${n.substring(0,e)+"***"}@${r}`}function f(t){if(null==t)return t;if("string"==typeof t)return t.includes("@")&&t.includes(".")?u(t):t;if(Array.isArray(t))return t.map(t=>f(t));if("object"==typeof t){const n={};for(const[r,e]of Object.entries(t))n[r]="email"===r&&"string"==typeof e?u(e):"consumerId"===r&&"string"==typeof e||"userId"===r&&"string"==typeof e?e.substring(0,8)+"***":f(e);return n}return t}export{e as deepClone,n as generateId,o as isBrowser,i as isStorageAvailable,r as jitteredIntervalMs,u as maskEmail,f as maskPII};
1
+ import{v7 as r}from"uuid";import{ErrorCodes as t}from"./errors.js";function e(){return r()}const n=(r,t=.2,e=Math.random)=>Math.round(r*(1-t+2*e()*t));function o(r,t=/* @__PURE__ */new WeakSet){if(null===r||"object"!=typeof r)return r;if(t.has(r))return r;if(r instanceof Date)return new Date(r.getTime());if(r instanceof Array)return t.add(r),r.map(r=>o(r,t));if(r instanceof Object){t.add(r);const e={};for(const n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=o(r[n],t));return e}return r}function i(){return"undefined"!=typeof window&&void 0!==window.document}function a(r){if(!i())return!1;try{const t=window[r],e="__storage_test__";return t.setItem(e,e),t.removeItem(e),!0}catch{return!1}}const s=new Set("adapterId appointmentId attempt auctionId bytes code correlationId distributionId drawId endpoint expected experienceId id issues journeyId kind locationId organizationId path reason requestId retryAfterMs slotId status statusCode type waitlistId".split(" ")),u=Object.values(t),c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function d(r){const t={error:"|AbortError|DOMException|Error|FanfareError|NoSequenceAvailableError|ProtocolError|RangeError|SequenceFullError|SnapshotInvariantError|SyntaxError|TypeError|ValiError|ValidationError|WaitlistClosedError|".includes(`|${r.name}|`)?r.name:"<unknown>"},{code:e,endpoint:n,status:o,requestId:i,correlationId:a}=r;return"string"==typeof e&&u.includes(e)&&(t.code=e),"string"==typeof n&&(t.endpoint=n),Number.isInteger(o)&&(t.status=o),"string"==typeof i&&c.test(i)&&(t.requestId=i),"string"==typeof a&&c.test(a)&&(t.correlationId=a),t}function f(r){if(r instanceof Error)return d(r);if(Array.isArray(r))return r.map(r=>f(r));if(null!==r&&"object"==typeof r){const t={};for(const[e,n]of Object.entries(r)){if(n instanceof Error){t[e]=d(n);continue}if(!s.has(e))continue;if("string"==typeof n||"number"==typeof n||"boolean"==typeof n){t[e]=n;continue}const r=f(n);void 0!==r&&(t[e]=r)}return t}}export{o as deepClone,e as generateId,i as isBrowser,a as isStorageAvailable,n as jitteredIntervalMs,f as maskPII};
@@ -1 +1 @@
1
- import{createError as e}from"../core/errors.js";import{buildGrantPaymentAffordance as t}from"../experiences/sequence-driver.js";import{resolveCheckoutContract as i,checkoutViewFields as n,isEntryActionRequired as s,canReenter as r,isPreAuthMisconfigured as o}from"../payment/checkout-contract.js";import{isClaimableSettlement as a}from"../payment/settlement.js";import{isUnresolvedDrawConsumer as d}from"./draw.sequence.js";import{drawSelectionFromResponse as c}from"./types.js";function u(e){return o(i(e.details))}function p(e){return r(e.distribution.lifecycle,i(e.distribution.details))}function l(e){return new w(e)}function m(t){const{distribution:i,consumer:n,grant:s,checkout:r,settlement:o,selection:a,outcome:c}=t;if(!i)return{phase:"unavailable"};const p=function(t){if("draw"!==t.type)throw e.validationError("Draw driver received a non-draw distribution");return t}(i),l=n&&"draw"===n.mechanism?function(e){const{mechanism:t,...i}=e;return i}(n):b(i);if(s)return"completed"===c?.type?y(p,l,c,r):s.expiresAt&&Date.now()>new Date(s.expiresAt).getTime()?"open"===p.lifecycle?u(p)?{phase:"unavailable",reason:"checkout_misconfigured"}:{phase:"enterable",mechanism:"draw",distribution:p,consumer:b(p)}:y(p,l,{type:"expired"}):o&&a?{phase:"granted",mechanism:"draw",distribution:p,consumer:l,grant:s,settlement:o,selection:a}:{phase:"unavailable"};if(!c&&"draw"===n?.mechanism&&"closed"===p.lifecycle)return y(p,l,{type:"lost"});const m=c??function(e){return"completed"===e.status?{type:"completed"}:"denied"===e.status?{type:"denied",reason:e.deniedReason}:void 0}(l);return m?y(p,l,m,r):"settling"===p.lifecycle?{phase:"settling",mechanism:"draw",distribution:p,consumer:l}:d(l)?{phase:"participating",mechanism:"draw",distribution:p,consumer:l}:"scheduled"===p.lifecycle?{phase:"scheduled",mechanism:"draw",distribution:p}:"open"===p.lifecycle?u(p)?{phase:"unavailable",reason:"checkout_misconfigured"}:{phase:"enterable",mechanism:"draw",distribution:p,consumer:l}:y(p,l,{type:"closed"})}function h(e,t){return e&&"draw"===e.mechanism&&t?"denied"===t.type?{...e,status:"denied",deniedReason:t.reason}:"completed"===t.type?{...e,status:"completed"}:"won"===t.type?{...e,status:"won"}:{...e,status:"not_entered"}:e}class w{constructor(e){this.deps=e,this.mechanism="draw",this.settlementCanStartWhileActive=!0}project(e){return m(e)}buildView(i,n){if("unavailable"===i.phase)return{phase:"unavailable",reason:i.reason};const s=function(t){if(!("mechanism"in t)||"draw"!==t.mechanism)throw e.validationError("Draw driver received a non-draw sequence");return t}(i);switch(s.phase){case"scheduled":return{phase:"scheduled",mechanism:"draw",distribution:s.distribution,startsAt:s.distribution.startsAt??s.distribution.opensAt};case"enterable":return this.buildEnterableView(s,n);case"participating":return{phase:"participating",mechanism:"draw",consumer:s.consumer,display$:n.displayAtom,loadSelectionOptions:n.loadSelectionOptions,select:n.select,leave:()=>this.leave()};case"settling":return{phase:"settling",mechanism:"draw",distribution:s.distribution,consumer:s.consumer};case"granted":{const e={phase:"granted",mechanism:"draw",distribution:s.distribution,consumer:s.consumer,grant:s.grant},{settlement:i}=s,r=s.selection;if("awaiting_assignment"===r.status)return{...e,settlement:i,selection:r};if("required"===r.status)return{...e,settlement:i,selection:r,select:n.select,loadSelectionOptions:n.loadSelectionOptions};if("internal"===i.processor&&"at_end"===i.mode){const o=t(i,s.pendingAction,n);return o?{...e,settlement:i,selection:r,loadSelectionOptions:n.loadSelectionOptions,select:n.select,claim:()=>n.claim(),payment:o}:{...e,settlement:i,selection:r,loadSelectionOptions:n.loadSelectionOptions,select:n.select,claim:()=>n.claim()}}return a(i)?{...e,settlement:i,selection:r,loadSelectionOptions:n.loadSelectionOptions,select:n.select,claim:()=>n.claim()}:{...e,settlement:i,selection:r}}case"ended":return this.buildEndedView(s)}}buildEnterableView(e,t){const s=i(e.distribution.details),r=n(s);return"pre_auth"===r.checkoutMode?{phase:"enterable",mechanism:"draw",...r,distribution:e.distribution,consumer:e.consumer,...e.pendingAction?{pendingAction:e.pendingAction}:{},...e.paymentProvider?{paymentProvider:e.paymentProvider}:{},loadSelectionOptions:t.loadSelectionOptions,enter:(e,t)=>this.enter({paymentInput:e,selection:t})}:{phase:"enterable",mechanism:"draw",...r,distribution:e.distribution,consumer:e.consumer,loadSelectionOptions:t.loadSelectionOptions,enter:e=>this.enter({selection:e})}}buildEndedView(e){const t={phase:"ended",mechanism:"draw",consumer:e.consumer,outcome:e.outcome,...e.checkout?{checkout:e.checkout}:{}};if(!p(e))return t;const s=i(e.distribution.details),r=n(s);return"pre_auth"===r.checkoutMode?{...t,...r,reenter:(e,t)=>this.enter({paymentInput:e,selection:t})}:{...t,...r,reenter:e=>this.enter({selection:e})}}startMonitoring(e){this.activeMonitorId=e.participation.id;const t=this.deps.getCurrentSequence(),i=t&&("participating"===t.phase||"settling"===t.phase)&&"mechanism"in t&&"draw"===t.mechanism&&"draw"===t.distribution.details?.type?t.distribution.details.drawTime:void 0;this.deps.draws.startMonitoring(e.participation.id,{displayAtom:e.displayAtom,drawTime:i,phase:"settling"===t?.phase?"settling":"participating"},e.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.draws.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(e){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation(e)})}async enterWithinOperation(t){const n=this.currentDrawSequence("enterable","No open draw to enter"),r=n.distribution,o=i(r.details),a=t?.paymentInput;if("pre_auth"===o.mode&&!a)throw e.validationError("Payment input is required for pre-auth distributions");const d=t?.selection?await this.deps.draws.enter(r.id,void 0,void 0,a,t.selection):a?await this.deps.draws.enter(r.id,void 0,void 0,a):await this.deps.draws.enter(r.id,void 0);if(s(d))return this.deps.commit({...n,pendingAction:d.nextAction}),void this.deps.emitEvent("sequence_change","info","user","Entry awaiting verification",{distributionId:r.id,processor:d.nextAction.processor});const u=function(e){if("ENTERED"===e.status)return null;if("COMPLETED"===e.status)return{type:"ended",outcome:{type:"completed"}};if("WON"===e.status)return"admissionGrant"in e?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null;return{type:"ended",outcome:{type:"DENIED"===e.status?"denied":"lost",reason:"reason"in e?e.reason:void 0}}}(d);if(u)return void this.deps.applyMonitorUpdate(u);const p=c(d),l={...(h=r.id,{mechanism:"draw",status:"entered",id:h,distributionId:h}),...p?{selection:p}:{}};var h;this.deps.ensureDisplayAtom({id:r.id,type:"draw"}),this.deps.commit(m({distribution:r,consumer:l})),this.deps.emitEvent("sequence_change","success","user","Entered draw",{distributionId:r.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const e=this.currentDrawSequence("participating"),t=e.consumer.distributionId??e.consumer.id??e.distribution.id;this.deps.stopRuntimeMonitoring(!0),await this.deps.draws.leave(t),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"draw"})}currentDrawSequence(t,i="Not participating in a draw"){const n=this.deps.getCurrentSequence();if(!n||!("mechanism"in n)||"draw"!==n.mechanism)throw e.validationError(i);if("enterable"===t&&"ended"===n.phase&&p(n))return{phase:"enterable",mechanism:"draw",distribution:n.distribution,consumer:b(n.distribution)};if(n.phase!==t)throw e.validationError(i);return n}}function b(e){return{status:"not_entered",distributionId:e.id}}function y(e,t,i,n){return{phase:"ended",mechanism:"draw",distribution:e,consumer:t,outcome:{...i,distributionType:i.distributionType??e.type,distributionId:i.distributionId??e.id,at:i.at??/* @__PURE__ */(new Date).toISOString()},...n?{checkout:n}:{}}}export{l as createDrawDriver,m as projectDrawSequence,h as withDrawTerminalConsumer};
1
+ import{DrawConsumerStatuses as e}from"@fanfare-io/fanfare-sdk-contracts/consumer-me";import{createError as t}from"../core/errors.js";import{selectionModeFields as i,buildGrantPaymentAffordance as n}from"../experiences/sequence-driver.js";import{resolveCheckoutContract as s,checkoutViewFields as r,isEntryActionRequired as o,canReenter as a,isPreAuthMisconfigured as d}from"../payment/checkout-contract.js";import{isClaimableSettlement as c}from"../payment/settlement.js";import{isUnresolvedDrawConsumer as u}from"./draw.sequence.js";import{drawSelectionFromResponse as p}from"./types.js";function l(e){return d(s(e.details))}function m(e){return a(e.distribution.lifecycle,s(e.distribution.details))}function h(e){return new y(e)}const w=new Set(e);function f(e){const n=function(e){const{distribution:i,consumer:n,grant:s,checkout:r,settlement:o,selection:a,outcome:d}=e;if(!i)return{phase:"unavailable"};const c=function(e){if("draw"!==e.type)throw t.validationError("Draw driver received a non-draw distribution");return e}(i),p=n&&"draw"===n.mechanism?function(e){const{mechanism:t,...i}=e;return i}(n):v(i);if(s)return"completed"===d?.type?g(c,p,d,r):s.expiresAt&&Date.now()>new Date(s.expiresAt).getTime()?"open"===c.lifecycle?l(c)?{phase:"unavailable",reason:"checkout_misconfigured"}:{phase:"enterable",mechanism:"draw",distribution:c,consumer:v(c)}:g(c,p,{type:"expired"}):o&&a?{phase:"granted",mechanism:"draw",distribution:c,consumer:p,grant:s,settlement:o,selection:a}:{phase:"unavailable"};if(!function(e){return w.has(e.status)}(p))return{phase:"unavailable",reason:"unrecognized_standing"};if(!d&&"draw"===n?.mechanism&&"closed"===c.lifecycle)return g(c,p,{type:"lost"});const m=d??function(e){return"completed"===e.status?{type:"completed"}:"denied"===e.status?{type:"denied",reason:e.deniedReason}:void 0}(p);if(m)return g(c,p,m,r);if("settling"===c.lifecycle)return{phase:"settling",mechanism:"draw",distribution:c,consumer:p};if(u(p))return{phase:"participating",mechanism:"draw",distribution:c,consumer:p};if("scheduled"===c.lifecycle)return{phase:"scheduled",mechanism:"draw",distribution:c};if("open"===c.lifecycle)return l(c)?{phase:"unavailable",reason:"checkout_misconfigured"}:{phase:"enterable",mechanism:"draw",distribution:c,consumer:p};return g(c,p,{type:"closed"})}(e);return"unavailable"===n.phase?n:{...n,...i(e)}}function b(e,t){return e&&"draw"===e.mechanism&&t?"left"===t.type?{...e,status:"left"}:"denied"===t.type?{...e,status:"denied",deniedReason:t.reason}:"completed"===t.type?{...e,status:"completed"}:"won"===t.type?{...e,status:"won"}:{...e,status:"not_entered"}:e}class y{constructor(e){this.deps=e,this.mechanism="draw",this.settlementCanStartWhileActive=!0}project(e){return f(e)}buildView(e,s){if("unavailable"===e.phase)return{phase:"unavailable",reason:e.reason};const r=function(e){if(!("mechanism"in e)||"draw"!==e.mechanism)throw t.validationError("Draw driver received a non-draw sequence");return e}(e);switch(r.phase){case"scheduled":return{phase:"scheduled",mechanism:"draw",...i(r),distribution:r.distribution,startsAt:r.distribution.startsAt??r.distribution.opensAt};case"enterable":return this.buildEnterableView(r,s);case"participating":return{phase:"participating",mechanism:"draw",...i(r),consumer:r.consumer,display$:s.displayAtom,loadSelectionOptions:s.loadSelectionOptions,select:s.select,leave:()=>this.leave()};case"settling":return{phase:"settling",mechanism:"draw",...i(r),distribution:r.distribution,consumer:r.consumer};case"granted":{const e={phase:"granted",mechanism:"draw",...i(r),distribution:r.distribution,consumer:r.consumer,grant:r.grant},{settlement:t}=r,o=r.selection;if("awaiting_assignment"===o.status)return{...e,settlement:t,selection:o};if("required"===o.status)return{...e,settlement:t,selection:o,select:s.select,loadSelectionOptions:s.loadSelectionOptions};if("internal"===t.processor&&"at_end"===t.mode){const i=n(t,r.pendingAction,s);return i?{...e,settlement:t,selection:o,loadSelectionOptions:s.loadSelectionOptions,select:s.select,claim:()=>s.claim(),payment:i}:{...e,settlement:t,selection:o,loadSelectionOptions:s.loadSelectionOptions,select:s.select,claim:()=>s.claim()}}return c(t)?{...e,settlement:t,selection:o,loadSelectionOptions:s.loadSelectionOptions,select:s.select,claim:()=>s.claim()}:{...e,settlement:t,selection:o}}case"ended":return this.buildEndedView(r)}}buildEnterableView(e,t){const n=s(e.distribution.details),o=r(n);return"pre_auth"===o.checkoutMode?{phase:"enterable",mechanism:"draw",...i(e),...o,distribution:e.distribution,consumer:e.consumer,...e.pendingAction?{pendingAction:e.pendingAction}:{},...e.paymentProvider?{paymentProvider:e.paymentProvider}:{},loadSelectionOptions:t.loadSelectionOptions,enter:(e,t)=>this.enter({paymentInput:e,selection:t})}:{phase:"enterable",mechanism:"draw",...i(e),...o,distribution:e.distribution,consumer:e.consumer,loadSelectionOptions:t.loadSelectionOptions,enter:e=>this.enter({selection:e})}}buildEndedView(e){const t={phase:"ended",mechanism:"draw",...i(e),consumer:e.consumer,outcome:e.outcome,...e.checkout?{checkout:e.checkout}:{}};if(!m(e))return t;const n=s(e.distribution.details),o=r(n);return"pre_auth"===o.checkoutMode?{...t,...o,reenter:(e,t)=>this.enter({paymentInput:e,selection:t})}:{...t,...o,reenter:e=>this.enter({selection:e})}}startMonitoring(e){this.activeMonitorId=e.participation.id;const t=this.deps.getCurrentSequence(),i=t&&("participating"===t.phase||"settling"===t.phase)&&"mechanism"in t&&"draw"===t.mechanism&&"draw"===t.distribution.details?.type?t.distribution.details.drawTime:void 0;this.deps.draws.startMonitoring(e.participation.id,{displayAtom:e.displayAtom,drawTime:i,phase:"settling"===t?.phase?"settling":"participating"},e.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.draws.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(e){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation(e)})}async enterWithinOperation(e){const i=this.currentDrawSequence("enterable","No open draw to enter"),n=i.distribution,r=s(n.details),a=e?.paymentInput;if("pre_auth"===r.mode&&!a)throw t.validationError("Payment input is required for pre-auth distributions");const d=e?.selection?await this.deps.draws.enter(n.id,void 0,void 0,a,e.selection):a?await this.deps.draws.enter(n.id,void 0,void 0,a):await this.deps.draws.enter(n.id,void 0);if(o(d))return this.deps.commit({...i,pendingAction:d.nextAction}),void this.deps.emitEvent("sequence_change","info","user","Entry awaiting verification",{distributionId:n.id,processor:d.nextAction.processor});const c=function(e){if("ENTERED"===e.status)return null;if("COMPLETED"===e.status)return{type:"ended",outcome:{type:"completed"}};if("WON"===e.status)return"admissionGrant"in e?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null;if("LEFT"===e.status)return{type:"ended",outcome:{type:"left"}};return{type:"ended",outcome:{type:"DENIED"===e.status?"denied":"lost",reason:"reason"in e?e.reason:void 0}}}(d);if(c)return void this.deps.applyMonitorUpdate(c);const u=p(d),l={...(m=n.id,{mechanism:"draw",status:"entered",id:m,distributionId:m}),...u?{selection:u}:{}};var m;this.deps.ensureDisplayAtom({id:n.id,type:"draw"}),this.deps.commit(f({distribution:n,consumer:l})),this.deps.emitEvent("sequence_change","success","user","Entered draw",{distributionId:n.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const e=this.currentDrawSequence("participating"),t=e.consumer.distributionId??e.consumer.id??e.distribution.id;await this.deps.draws.leave(t),this.deps.stopRuntimeMonitoring(!0),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"draw"})}currentDrawSequence(e,i="Not participating in a draw"){const n=this.deps.getCurrentSequence();if(!n||!("mechanism"in n)||"draw"!==n.mechanism)throw t.validationError(i);if("enterable"===e&&"ended"===n.phase&&m(n))return{phase:"enterable",mechanism:"draw",distribution:n.distribution,consumer:v(n.distribution)};if(n.phase!==e)throw t.validationError(i);return n}}function v(e){return{status:"not_entered",distributionId:e.id}}function g(e,t,i,n){return{phase:"ended",mechanism:"draw",distribution:e,consumer:t,outcome:{...i,distributionType:i.distributionType??e.type,distributionId:i.distributionId??e.id,at:i.at??/* @__PURE__ */(new Date).toISOString()},...n?{checkout:n}:{}}}export{h as createDrawDriver,f as projectDrawSequence,b as withDrawTerminalConsumer};
@@ -1 +1 @@
1
- import{createError as t,isFanfareError as e}from"../core/errors.js";import{getLogger as i}from"../core/logger.js";import{jitteredIntervalMs as s}from"../core/utils.js";import{DistributionMonitorRuntime as r}from"../experiences/distribution-monitor.runtime.js";import{isEntryActionRequired as n}from"../payment/checkout-contract.js";import{getEventBus as a}from"../state/events.js";import{getSDKStore as o}from"../state/store.js";const h=2147483647;class c{constructor(t){this.logger=i(),this.events=a(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.scheduledChecks=/* @__PURE__ */new Map,this.settlingTimers=/* @__PURE__ */new Map,this.monitorRuntime=new r,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.drawTimeMs=/* @__PURE__ */new Map,this.initialCheckBufferMs=5e3,this.retryCheckDelayMs=5e3,this.retryAfterCapMs=6e4,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return o()}async get(t){try{return await this.http.get(`/draws/${t}`)}catch(e){throw this.logger.error("Failed to get draw",{drawId:t,error:e}),e}}async enter(e,i,s,r,a,o){try{const h=this.lifecycleGeneration;if(!this.store.session)throw t.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const c=this.bumpOperationGeneration(e);this.logger.info("Entering draw",{drawId:e});const d={metadata:i,selection:a};r&&(d.paymentInput=r);const l=await this.http.post(`/draws/${e}/enter`,d,s);if(n(l))return l;const u=this.buildTransition(e,l,{source:"enter",enteredAt:/* @__PURE__ */(new Date).toISOString(),metadata:i});return"ENTERED"!==l.status&&"WON"!==l.status&&"COMPLETED"!==l.status||u.events.push({type:"experience:distribution-entered",payload:{distributionType:"draw",distributionId:e,...o}}),this.canApplyAsyncResult(e,h,void 0,c)?(this.applyTransition(e,u),l):l}catch(h){throw this.logger.error("Failed to enter draw",{drawId:e,error:h}),this.events.emit("draw:error",{drawId:e,error:h}),h}}async leave(t,e){try{const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);if(this.logger.info("Leaving draw",{drawId:t}),await this.http.post(`/draws/${t}/leave`,void 0,e),!this.canApplyAsyncResult(t,i,void 0,s))return;this.trackedParticipations.delete(t),this.stopMonitoring(t),this.events.emit("draw:left",{drawId:t})}catch(i){throw this.logger.error("Failed to leave draw",{drawId:t,error:i}),this.events.emit("draw:error",{drawId:t,error:i}),i}}async status(t){const e=this.inFlightRequests.get(t);if(e)return this.logger.debug("Returning existing in-flight request",{drawId:t}),e;const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),r=this.doStatus(t,i,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,r),r}async doStatus(t,e,i,s){try{const r=await this.http.get(`/draws/${t}/status`);if(!this.canApplyAsyncResult(t,e,i,s))return r;const n=this.buildTransition(t,r,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),this.events.emit("draw:status-updated",{drawId:t,status:r.status}),r}catch(r){if(r&&"object"==typeof r&&"status"in r&&404===r.status){const r={status:"NOT_ENTERED"};if(!this.canApplyAsyncResult(t,e,i,s))return r;const n=this.buildTransition(t,r,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),this.events.emit("draw:status-updated",{drawId:t,status:r.status}),r}if(!this.canApplyAsyncResult(t,e,i,s))return this.trackedParticipations.has(t)?{status:"ENTERED"}:{status:"NOT_ENTERED"};throw this.logger.error("Failed to get draw status",{drawId:t,error:r}),this.events.emit("draw:error",{drawId:t,error:r}),r}}startMonitoring(t,e,i){this.monitorRuntime.start(t,e,i),this.bumpMonitorGeneration(t);const s=e?.drawTime;s?this.drawTimeMs.set(t,new Date(s).getTime()):this.drawTimeMs.delete(t);const r=this.monitorRuntime.getDisplayAtom(t);r&&r.set({type:"draw",drawTime:s,entryNumber:this.trackedParticipations.get(t)?.entryNumber,selectionAvailability:this.trackedParticipations.get(t)?.selectionAvailability}),s&&"settling"!==e?.phase?this.scheduleSettlingTransition(t,s):this.cancelSettlingTransition(t),"settling"===e?.phase?this.scheduleStatusCheck(t,0):s&&this.scheduleInitialStatusCheck(t,s)}stopMonitoring(t){this.bumpMonitorGeneration(t),this.cancelScheduledCheck(t),this.cancelSettlingTransition(t),this.drawTimeMs.delete(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.scheduledChecks.has(t)||this.settlingTimers.has(t)}buildTransition(t,e,{source:i,previousParticipation:s,enteredAt:r,metadata:n}){switch(e.status){case"ENTERED":return{selectionAvailability:e.selectionAvailability,trackedParticipation:{drawId:t,enteredAt:s?.enteredAt??r??/* @__PURE__ */(new Date).toISOString(),entryNumber:s?.entryNumber,selectionAvailability:e.selectionAvailability,metadata:s?.metadata??n},events:"enter"===i?[{type:"draw:entered",payload:{drawId:t}}]:[],monitorUpdate:null,stopMonitoring:!1,retryStatusCheck:"status"===i};case"WON":return{trackedParticipation:null,events:[{type:"draw:won",payload:{drawId:t,result:{won:!0,drawTime:e.wonAt}}}],monitorUpdate:"status"===i?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"COMPLETED":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"completed"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"DENIED":return{trackedParticipation:null,events:[{type:"draw:expired",payload:{drawId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"NOT_ENTERED":return{trackedParticipation:null,events:s?[{type:"draw:lost",payload:{drawId:t,result:this.buildLostResult()}}]:[],monitorUpdate:"status"===i?{type:"ended",outcome:s?{type:"lost"}:{type:"closed",reason:"not_entered"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1}}}applyTransition(t,e){const i=this.monitorRuntime.getDisplayAtom(t),s=i?.get();i&&"draw"===s?.type&&void 0!==e.selectionAvailability&&i.set({...s,selectionAvailability:e.selectionAvailability}),e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t);for(const r of e.events)this.events.emit(r.type,r.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring?this.stopMonitoring(t):e.retryStatusCheck&&this.monitorRuntime.has(t)&&this.scheduleRetryStatusCheck(t)}scheduleInitialStatusCheck(t,e){const i=new Date(e).getTime(),s=Date.now(),r=i+this.initialCheckBufferMs,n=r<=s?s:r+Math.round(2e3*Math.random());this.scheduleStatusCheckAt(t,n)}scheduleSettlingTransition(t,e){const i=new Date(e).getTime(),s=Math.max(0,i-Date.now());this.cancelSettlingTransition(t);const r=this.monitorGenerations.get(t),n=setTimeout(()=>{this.settlingTimers.delete(t),void 0!==r&&this.monitorGenerations.get(t)===r&&(Date.now()<i?this.scheduleSettlingTransition(t,e):this.monitorRuntime.notify(t,{type:"settling",at:e},!1))},Math.min(s,h));this.settlingTimers.set(t,n)}cancelSettlingTransition(t){const e=this.settlingTimers.get(t);e&&clearTimeout(e),this.settlingTimers.delete(t)}scheduleRetryStatusCheck(t,i){const r=(e(i)&&"number"==typeof i.retryAfter&&Number.isFinite(i.retryAfter)?Math.min(1e3*i.retryAfter,this.retryAfterCapMs):void 0)??s(this.retryCheckDelayMs);this.scheduleStatusCheck(t,this.isInSettlingWindow(t)?Math.min(r,this.retryCheckDelayMs):r)}isInSettlingWindow(t){const e=this.drawTimeMs.get(t);return void 0!==e&&Date.now()>=e}scheduleStatusCheck(t,i){this.cancelScheduledCheck(t);const s=this.monitorGenerations.get(t),r=this.getOperationGeneration(t),n=setTimeout(async()=>{this.scheduledChecks.delete(t);try{if(void 0===s)return;await this.doStatus(t,this.lifecycleGeneration,s,r)}catch(i){if(!this.canApplyAsyncResult(t,this.lifecycleGeneration,s,r))return;if(e(i)&&i.retryable&&this.monitorRuntime.has(t))return void this.scheduleRetryStatusCheck(t,i);this.stopMonitoring(t)}},i);this.scheduledChecks.set(t,n)}scheduleStatusCheckAt(t,e){const i=Math.max(0,e-Date.now());if(i<=h)return void this.scheduleStatusCheck(t,i);this.cancelScheduledCheck(t);const s=this.monitorGenerations.get(t),r=setTimeout(()=>{this.scheduledChecks.delete(t),void 0!==s&&this.monitorGenerations.get(t)===s&&this.scheduleStatusCheckAt(t,e)},h);this.scheduledChecks.set(t,r)}cancelScheduledCheck(t){const e=this.scheduledChecks.get(t);e&&(clearTimeout(e),this.scheduledChecks.delete(t))}buildLostResult(){return{won:!1,drawTime:/* @__PURE__ */(new Date).toISOString()}}bumpMonitorGeneration(t){const e=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,e),e}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const e=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,e),e}canApplyAsyncResult(t,e,i,s){return!this.destroyed&&this.lifecycleGeneration===e&&((void 0===i||this.monitorGenerations.get(t)===i)&&(void 0===s||this.getOperationGeneration(t)===s))}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.scheduledChecks.forEach(t=>clearTimeout(t)),this.scheduledChecks.clear(),this.settlingTimers.forEach(t=>clearTimeout(t)),this.settlingTimers.clear(),this.inFlightRequests.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.drawTimeMs.clear(),this.monitorRuntime.clear()}}export{c as DrawManagementModule};
1
+ import{createError as t,isFanfareError as e}from"../core/errors.js";import{getLogger as i}from"../core/logger.js";import{jitteredIntervalMs as s}from"../core/utils.js";import{DistributionMonitorRuntime as r}from"../experiences/distribution-monitor.runtime.js";import{isEntryActionRequired as n}from"../payment/checkout-contract.js";import{getEventBus as a}from"../state/events.js";import{getSDKStore as o}from"../state/store.js";const h=2147483647;class c{constructor(t){this.logger=i(),this.events=a(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.scheduledChecks=/* @__PURE__ */new Map,this.settlingTimers=/* @__PURE__ */new Map,this.monitorRuntime=new r,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.drawTimeMs=/* @__PURE__ */new Map,this.initialCheckBufferMs=5e3,this.retryCheckDelayMs=5e3,this.retryAfterCapMs=6e4,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return o()}async get(t){try{return await this.http.get(`/draws/${t}`)}catch(e){throw this.logger.error("Failed to get draw",{drawId:t,error:e}),e}}async enter(e,i,s,r,a,o){try{const h=this.lifecycleGeneration;if(!this.store.session)throw t.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const c=this.bumpOperationGeneration(e);this.logger.info("Entering draw",{drawId:e});const d={metadata:i,selection:a};r&&(d.paymentInput=r);const l=await this.http.post(`/draws/${e}/enter`,d,s);if(n(l))return l;const u=this.buildTransition(e,l,{source:"enter",enteredAt:/* @__PURE__ */(new Date).toISOString(),metadata:i});return"ENTERED"!==l.status&&"WON"!==l.status&&"COMPLETED"!==l.status||u.events.push({type:"experience:distribution-entered",payload:{distributionType:"draw",distributionId:e,...o}}),this.canApplyAsyncResult(e,h,void 0,c)?(this.applyTransition(e,u),l):l}catch(h){throw this.logger.error("Failed to enter draw",{drawId:e,error:h}),this.events.emit("draw:error",{drawId:e,error:h}),h}}async leave(t,e){try{const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);if(this.logger.info("Leaving draw",{drawId:t}),await this.http.post(`/draws/${t}/leave`,void 0,e),!this.canApplyAsyncResult(t,i,void 0,s))return;this.trackedParticipations.delete(t),this.stopMonitoring(t),this.events.emit("draw:left",{drawId:t})}catch(i){throw this.logger.error("Failed to leave draw",{drawId:t,error:i}),this.events.emit("draw:error",{drawId:t,error:i}),i}}async status(t){const e=this.inFlightRequests.get(t);if(e)return this.logger.debug("Returning existing in-flight request",{drawId:t}),e;const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),r=this.doStatus(t,i,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,r),r}async doStatus(t,e,i,s){try{const r=await this.http.get(`/draws/${t}/status`);if(!this.canApplyAsyncResult(t,e,i,s))return r;const n=this.buildTransition(t,r,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),this.events.emit("draw:status-updated",{drawId:t,status:r.status}),r}catch(r){if(r&&"object"==typeof r&&"status"in r&&404===r.status){const r={status:"NOT_ENTERED"};if(!this.canApplyAsyncResult(t,e,i,s))return r;const n=this.buildTransition(t,r,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),this.events.emit("draw:status-updated",{drawId:t,status:r.status}),r}if(!this.canApplyAsyncResult(t,e,i,s))return this.trackedParticipations.has(t)?{status:"ENTERED"}:{status:"NOT_ENTERED"};throw this.logger.error("Failed to get draw status",{drawId:t,error:r}),this.events.emit("draw:error",{drawId:t,error:r}),r}}startMonitoring(t,e,i){this.monitorRuntime.start(t,e,i),this.bumpMonitorGeneration(t);const s=e?.drawTime;s?this.drawTimeMs.set(t,new Date(s).getTime()):this.drawTimeMs.delete(t);const r=this.monitorRuntime.getDisplayAtom(t);r&&r.set({type:"draw",drawTime:s,entryNumber:this.trackedParticipations.get(t)?.entryNumber,selectionAvailability:this.trackedParticipations.get(t)?.selectionAvailability}),s&&"settling"!==e?.phase?this.scheduleSettlingTransition(t,s):this.cancelSettlingTransition(t),"settling"===e?.phase?this.scheduleStatusCheck(t,0):s&&this.scheduleInitialStatusCheck(t,s)}stopMonitoring(t){this.bumpMonitorGeneration(t),this.cancelScheduledCheck(t),this.cancelSettlingTransition(t),this.drawTimeMs.delete(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.scheduledChecks.has(t)||this.settlingTimers.has(t)}buildTransition(t,e,{source:i,previousParticipation:s,enteredAt:r,metadata:n}){switch(e.status){case"ENTERED":return{selectionAvailability:e.selectionAvailability,trackedParticipation:{drawId:t,enteredAt:s?.enteredAt??r??/* @__PURE__ */(new Date).toISOString(),entryNumber:s?.entryNumber,selectionAvailability:e.selectionAvailability,metadata:s?.metadata??n},events:"enter"===i?[{type:"draw:entered",payload:{drawId:t}}]:[],monitorUpdate:null,stopMonitoring:!1,retryStatusCheck:"status"===i};case"WON":return{trackedParticipation:null,events:[{type:"draw:won",payload:{drawId:t,result:{won:!0,drawTime:e.wonAt}}}],monitorUpdate:"status"===i?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"COMPLETED":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"completed"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"DENIED":return{trackedParticipation:null,events:[{type:"draw:expired",payload:{drawId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"LEFT":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"left"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"NOT_ENTERED":return{trackedParticipation:null,events:s?[{type:"draw:lost",payload:{drawId:t,result:this.buildLostResult()}}]:[],monitorUpdate:"status"===i?{type:"ended",outcome:s?{type:"lost"}:{type:"closed",reason:"not_entered"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};default:return{trackedParticipation:s??null,events:[],monitorUpdate:null,stopMonitoring:!1,retryStatusCheck:!0}}}applyTransition(t,e){const i=this.monitorRuntime.getDisplayAtom(t),s=i?.get();i&&"draw"===s?.type&&void 0!==e.selectionAvailability&&i.set({...s,selectionAvailability:e.selectionAvailability}),e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t);for(const r of e.events)this.events.emit(r.type,r.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring?this.stopMonitoring(t):e.retryStatusCheck&&this.monitorRuntime.has(t)&&this.scheduleRetryStatusCheck(t)}scheduleInitialStatusCheck(t,e){const i=new Date(e).getTime(),s=Date.now(),r=i+this.initialCheckBufferMs,n=r<=s?s:r+Math.round(2e3*Math.random());this.scheduleStatusCheckAt(t,n)}scheduleSettlingTransition(t,e){const i=new Date(e).getTime(),s=Math.max(0,i-Date.now());this.cancelSettlingTransition(t);const r=this.monitorGenerations.get(t),n=setTimeout(()=>{this.settlingTimers.delete(t),void 0!==r&&this.monitorGenerations.get(t)===r&&(Date.now()<i?this.scheduleSettlingTransition(t,e):this.monitorRuntime.notify(t,{type:"settling",at:e},!1))},Math.min(s,h));this.settlingTimers.set(t,n)}cancelSettlingTransition(t){const e=this.settlingTimers.get(t);e&&clearTimeout(e),this.settlingTimers.delete(t)}scheduleRetryStatusCheck(t,i){const r=(e(i)&&"number"==typeof i.retryAfter&&Number.isFinite(i.retryAfter)?Math.min(1e3*i.retryAfter,this.retryAfterCapMs):void 0)??s(this.retryCheckDelayMs);this.scheduleStatusCheck(t,this.isInSettlingWindow(t)?Math.min(r,this.retryCheckDelayMs):r)}isInSettlingWindow(t){const e=this.drawTimeMs.get(t);return void 0!==e&&Date.now()>=e}scheduleStatusCheck(t,i){this.cancelScheduledCheck(t);const s=this.monitorGenerations.get(t),r=this.getOperationGeneration(t),n=setTimeout(async()=>{this.scheduledChecks.delete(t);try{if(void 0===s)return;await this.doStatus(t,this.lifecycleGeneration,s,r)}catch(i){if(!this.canApplyAsyncResult(t,this.lifecycleGeneration,s,r))return;if(e(i)&&i.retryable&&this.monitorRuntime.has(t))return void this.scheduleRetryStatusCheck(t,i);this.stopMonitoring(t)}},i);this.scheduledChecks.set(t,n)}scheduleStatusCheckAt(t,e){const i=Math.max(0,e-Date.now());if(i<=h)return void this.scheduleStatusCheck(t,i);this.cancelScheduledCheck(t);const s=this.monitorGenerations.get(t),r=setTimeout(()=>{this.scheduledChecks.delete(t),void 0!==s&&this.monitorGenerations.get(t)===s&&this.scheduleStatusCheckAt(t,e)},h);this.scheduledChecks.set(t,r)}cancelScheduledCheck(t){const e=this.scheduledChecks.get(t);e&&(clearTimeout(e),this.scheduledChecks.delete(t))}buildLostResult(){return{won:!1,drawTime:/* @__PURE__ */(new Date).toISOString()}}bumpMonitorGeneration(t){const e=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,e),e}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const e=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,e),e}canApplyAsyncResult(t,e,i,s){return!this.destroyed&&this.lifecycleGeneration===e&&((void 0===i||this.monitorGenerations.get(t)===i)&&(void 0===s||this.getOperationGeneration(t)===s))}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.scheduledChecks.forEach(t=>clearTimeout(t)),this.scheduledChecks.clear(),this.settlingTimers.forEach(t=>clearTimeout(t)),this.settlingTimers.clear(),this.inFlightRequests.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.drawTimeMs.clear(),this.monitorRuntime.clear()}}export{c as DrawManagementModule};
@@ -1,5 +1,5 @@
1
1
  import { AdmissionGrant, DrawConsumerStatus, GrantedSettlement, SequenceOutcome } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
2
- import { GrantedSelection } from '@fanfare-io/fanfare-sdk-contracts/selection';
2
+ import { GrantedSelection, SelectionMode } from '@fanfare-io/fanfare-sdk-contracts/selection';
3
3
  import { ReadableAtom } from 'nanostores';
4
4
  import { DrawDisplayState } from '../experiences/distribution-monitor.types';
5
5
  import { DistributionSummary } from '../experiences/journey.types';
@@ -48,6 +48,7 @@ export type DrawSequence = DrawScheduled | DrawEnterable | DrawParticipating | D
48
48
  export interface DrawScheduled {
49
49
  /** Mechanism discriminator for draw narrowing. */
50
50
  mechanism: "draw";
51
+ selectionMode?: SelectionMode;
51
52
  /** Phase discriminator; scheduled draws cannot be entered yet. */
52
53
  phase: "scheduled";
53
54
  /** Draw distribution facts from the journey projector. */
@@ -61,6 +62,7 @@ export interface DrawScheduled {
61
62
  export interface DrawEnterable {
62
63
  /** Mechanism discriminator for draw narrowing. */
63
64
  mechanism: "draw";
65
+ selectionMode?: SelectionMode;
64
66
  /** Phase discriminator; `enter()` is legal on the matching view. */
65
67
  phase: "enterable";
66
68
  /** Open draw distribution facts from the journey projector. */
@@ -87,6 +89,7 @@ export interface DrawEnterable {
87
89
  export interface DrawParticipating {
88
90
  /** Mechanism discriminator for draw narrowing. */
89
91
  mechanism: "draw";
92
+ selectionMode?: SelectionMode;
90
93
  /** Phase discriminator; the draw display is live while participating. */
91
94
  phase: "participating";
92
95
  /** Draw distribution facts from the journey projector. */
@@ -102,6 +105,7 @@ export interface DrawParticipating {
102
105
  export interface DrawSettling {
103
106
  /** Mechanism discriminator for draw narrowing. */
104
107
  mechanism: "draw";
108
+ selectionMode?: SelectionMode;
105
109
  /** Phase discriminator; no consumer action is legal while settling. */
106
110
  phase: "settling";
107
111
  /** Settling draw distribution facts from the journey projector. */
@@ -117,6 +121,7 @@ export interface DrawSettling {
117
121
  export interface DrawGranted {
118
122
  /** Mechanism discriminator for draw narrowing. */
119
123
  mechanism: "draw";
124
+ selectionMode?: SelectionMode;
120
125
  /** Phase discriminator; `claim()` is legal on the matching view. */
121
126
  phase: "granted";
122
127
  /** Draw distribution facts from the journey projector. */
@@ -143,6 +148,7 @@ export interface DrawGranted {
143
148
  export interface DrawEnded {
144
149
  /** Mechanism discriminator for draw narrowing. */
145
150
  mechanism: "draw";
151
+ selectionMode?: SelectionMode;
146
152
  /** Phase discriminator; no draw actions are legal after this phase. */
147
153
  phase: "ended";
148
154
  /** Draw distribution facts from the journey projector. */
@@ -172,6 +178,7 @@ export interface DrawScheduledView {
172
178
  phase: "scheduled";
173
179
  /** Mechanism discriminator for draw rendering. */
174
180
  mechanism: "draw";
181
+ selectionMode?: SelectionMode;
175
182
  /** Draw distribution facts from the journey projector. */
176
183
  distribution: DistributionSummary & {
177
184
  type: "draw";
@@ -193,6 +200,7 @@ export type DrawEnterableView = {
193
200
  phase: "enterable";
194
201
  /** Mechanism discriminator for draw rendering. */
195
202
  mechanism: "draw";
203
+ selectionMode?: SelectionMode;
196
204
  /** Money moves after the win; entry places no hold. */
197
205
  checkoutMode: "at_end";
198
206
  /** Open draw distribution facts from the journey projector. */
@@ -216,6 +224,7 @@ export type DrawEnterableView = {
216
224
  phase: "enterable";
217
225
  /** Mechanism discriminator for draw rendering. */
218
226
  mechanism: "draw";
227
+ selectionMode?: SelectionMode;
219
228
  /** Fanfare places a payment authorization hold at entry and captures it on win. */
220
229
  checkoutMode: "pre_auth";
221
230
  /** Open draw distribution facts from the journey projector. */
@@ -258,6 +267,7 @@ export interface DrawParticipatingView {
258
267
  phase: "participating";
259
268
  /** Mechanism discriminator for draw rendering. */
260
269
  mechanism: "draw";
270
+ selectionMode?: SelectionMode;
261
271
  /** Consumer draw participation facts. */
262
272
  consumer: DrawSequenceConsumerState;
263
273
  /** Live draw display facts, such as participant counts and draw timing. */
@@ -282,6 +292,7 @@ export interface DrawSettlingView {
282
292
  phase: "settling";
283
293
  /** Mechanism discriminator for draw rendering. */
284
294
  mechanism: "draw";
295
+ selectionMode?: SelectionMode;
285
296
  /** Settling draw distribution facts from the journey projector. */
286
297
  distribution: DistributionSummary & {
287
298
  type: "draw";
@@ -295,6 +306,7 @@ interface DrawGrantedViewBase {
295
306
  phase: "granted";
296
307
  /** Mechanism discriminator for draw rendering. */
297
308
  mechanism: "draw";
309
+ selectionMode?: SelectionMode;
298
310
  /** Draw distribution facts from the journey projector. */
299
311
  distribution: DistributionSummary & {
300
312
  type: "draw";
@@ -375,6 +387,7 @@ export type DrawEndedView = {
375
387
  phase: "ended";
376
388
  /** Mechanism discriminator for draw rendering. */
377
389
  mechanism: "draw";
390
+ selectionMode?: SelectionMode;
378
391
  /** Consumer draw facts at the terminal outcome. */
379
392
  consumer: DrawSequenceConsumerState;
380
393
  /** Server-derived terminal outcome for the draw participation. */
@@ -390,6 +403,7 @@ export type DrawEndedView = {
390
403
  phase: "ended";
391
404
  /** Mechanism discriminator for draw rendering. */
392
405
  mechanism: "draw";
406
+ selectionMode?: SelectionMode;
393
407
  /** Consumer draw facts at the terminal outcome. */
394
408
  consumer: DrawSequenceConsumerState;
395
409
  /** Server-derived terminal outcome for the draw participation. */
@@ -411,6 +425,7 @@ export type DrawEndedView = {
411
425
  phase: "ended";
412
426
  /** Mechanism discriminator for draw rendering. */
413
427
  mechanism: "draw";
428
+ selectionMode?: SelectionMode;
414
429
  /** Consumer draw facts at the terminal outcome. */
415
430
  consumer: DrawSequenceConsumerState;
416
431
  /** Server-derived terminal outcome for the draw participation. */
@@ -445,6 +460,7 @@ export interface DrawGrantedAwaitingAssignmentView {
445
460
  phase: "granted";
446
461
  /** Mechanism discriminator. */
447
462
  mechanism: "draw";
463
+ selectionMode?: SelectionMode;
448
464
  /** Distribution associated with this grant. */
449
465
  distribution: DistributionSummary & {
450
466
  type: "draw";
@@ -466,6 +482,7 @@ export interface DrawGrantedSelectionRequiredView {
466
482
  phase: "granted";
467
483
  /** Mechanism discriminator. */
468
484
  mechanism: "draw";
485
+ selectionMode?: SelectionMode;
469
486
  /** Distribution associated with this grant. */
470
487
  distribution: DistributionSummary & {
471
488
  type: "draw";
@@ -44,7 +44,7 @@ export interface DrawResult {
44
44
  /**
45
45
  * Draw consumer states (from backend)
46
46
  */
47
- export type DrawConsumerState = DrawNotEnteredState | DrawEnteredState | DrawWonState | DrawCompletedState | DrawDeniedState;
47
+ export type DrawConsumerState = DrawNotEnteredState | DrawEnteredState | DrawWonState | DrawCompletedState | DrawDeniedState | DrawLeftState;
48
48
  export interface DrawNotEnteredState {
49
49
  status: "NOT_ENTERED";
50
50
  }
@@ -70,6 +70,11 @@ export interface DrawCompletedState {
70
70
  fingerprint?: string;
71
71
  expiresAt?: string;
72
72
  }
73
+ /** A voluntary exit. Nothing refused the consumer, so there is no reason to carry. */
74
+ export interface DrawLeftState {
75
+ status: "LEFT";
76
+ leftAt: string;
77
+ }
73
78
  export interface DrawDeniedState {
74
79
  status: "DENIED";
75
80
  reason: string;