@fanfare-io/fanfare-sdk-core 0.15.0 → 0.16.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.
Files changed (49) hide show
  1. package/README.md +50 -0
  2. package/dist/auctions/auction-status.js +1 -1
  3. package/dist/auctions/auction.driver.js +1 -1
  4. package/dist/auctions/auction.module.js +1 -1
  5. package/dist/auctions/auction.sequence.d.ts +89 -20
  6. package/dist/auctions/auction.sequence.js +1 -1
  7. package/dist/auctions/dutch-pricing.js +1 -0
  8. package/dist/auctions/public.d.ts +1 -1
  9. package/dist/auctions/types.d.ts +56 -29
  10. package/dist/beacon/types.d.ts +1 -1
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.js +1 -1
  13. package/dist/core/client.js +2 -2
  14. package/dist/core/error-disposition.d.ts +9 -0
  15. package/dist/core/error-disposition.js +1 -1
  16. package/dist/core/errors.d.ts +0 -3
  17. package/dist/core/errors.js +1 -1
  18. package/dist/draws/draw.driver.js +1 -1
  19. package/dist/draws/draw.module.js +1 -1
  20. package/dist/draws/draw.sequence.d.ts +85 -10
  21. package/dist/draws/types.d.ts +10 -1
  22. package/dist/draws/types.js +1 -1
  23. package/dist/experiences/distribution-monitor.types.d.ts +76 -15
  24. package/dist/experiences/experience.module.js +1 -1
  25. package/dist/experiences/journey-view.js +1 -1
  26. package/dist/experiences/journey.js +1 -1
  27. package/dist/experiences/journey.machine.js +1 -1
  28. package/dist/experiences/journey.types.d.ts +4 -4
  29. package/dist/experiences/public.d.ts +4 -2
  30. package/dist/experiences/types.d.ts +0 -4
  31. package/dist/queues/queue.driver.js +1 -1
  32. package/dist/queues/queue.module.js +1 -1
  33. package/dist/queues/queue.sequence.d.ts +64 -3
  34. package/dist/queues/types.d.ts +1 -0
  35. package/dist/selections/public.d.ts +6 -0
  36. package/dist/selections/public.js +1 -0
  37. package/dist/selections/selection.module.d.ts +38 -0
  38. package/dist/selections/selection.module.js +1 -0
  39. package/dist/selections/types.d.ts +48 -0
  40. package/dist/ssr/ssr-sdk.js +1 -1
  41. package/dist/state/events.d.ts +0 -31
  42. package/dist/timed-releases/timed-release.driver.js +1 -1
  43. package/dist/timed-releases/timed-release.module.js +1 -1
  44. package/dist/timed-releases/timed-release.sequence.d.ts +43 -5
  45. package/dist/timed-releases/types.d.ts +6 -1
  46. package/dist/types/index.d.ts +3 -0
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +8 -4
package/README.md CHANGED
@@ -93,6 +93,56 @@ The framework adapters expose a tracker already bound to the surrounding experie
93
93
  `useBeaconTracker` from `@fanfare-io/fanfare-sdk-react` and `createBeaconTracker` from
94
94
  `@fanfare-io/fanfare-sdk-solid`.
95
95
 
96
+ ## Browser origins and public keys
97
+
98
+ Publishable keys identify your organization and are intended to appear in storefront
99
+ code. They are not passwords. Consumer authentication and any required admission or
100
+ capability proof still authorize protected operations.
101
+
102
+ The SDK's explicit-token API works from any browser origin; a headless storefront
103
+ does not need a per-customer CORS allowlist entry. The organization's **Embedding**
104
+ settings control which sites can frame a hosted Fanfare page, not which sites can call
105
+ the API or render the SDK directly. Neither CORS nor an embedding allowlist replaces
106
+ server-side authorization. Never put secret API keys in browser code.
107
+
108
+ ## Page observation
109
+
110
+ Use `journey.view$` for custom UI. The pure helpers `describeJourneyState(view)` and
111
+ `selectJourneyDisplay(view)` from `@fanfare-io/fanfare-sdk-core/experiences` describe
112
+ current state and select an available participating display atom, respectively.
113
+ They do not subscribe or start monitoring. React/Solid integrations should use their
114
+ existing journey hooks and `useSdkEvent` rather than use the DOM as a second state API.
115
+
116
+ For CSS and merchant scripts outside the SDK integration, install
117
+ `createDomBridge(journey, { target })` from that same public entry point. Use one
118
+ already-mounted, stable target per bridge; omitting it uses the document root.
119
+ The bridge returns `getState()` and an idempotent `dispose()`.
120
+
121
+ - Installation stamps current attributes without an initial event; subsequent
122
+ projection changes dispatch the bubbling, composed `fanfare:state-change` event.
123
+ - The attributes are `data-fanfare-experience-id`, `data-fanfare-stage`,
124
+ `data-fanfare-phase`, `data-fanfare-mechanism`, and `data-fanfare-expires-at` where
125
+ applicable. Expiry is Unix milliseconds. Ignore unknown attributes; absent ones
126
+ do not imply another state or SDK version.
127
+ - Queue position and terminal outcome type are payload-only; free-text outcome
128
+ reasons never cross the bridge. Read `getState()` when an immediate payload is needed.
129
+ - `@fanfare-io/fanfare-sdk-contracts/bridge` exports `FANFARE_STATE_CHANGE_EVENT`,
130
+ `BRIDGE_EVENT_VERSION`, `FanfareStateChangeDetail`, and `FanfareStateChangeEvent`
131
+ without importing the SDK. For a direct import, first run
132
+ `npm install @fanfare-io/fanfare-sdk-contracts`; a transitive dependency is not
133
+ an application import under strict package isolation. Check payload version `1` and use `detail.experienceId`
134
+ for attribution across shadow roots.
135
+ - Dispose before destroying the journey or SDK. This restores the bridge's attributes
136
+ and stops observation; it does not leave a distribution or unsubscribe a waitlist.
137
+
138
+ Once installed, surrounding CSS needs no SDK import:
139
+
140
+ ```css
141
+ [data-fanfare-stage="routed"][data-fanfare-phase="participating"] .entry-prompt {
142
+ display: none;
143
+ }
144
+ ```
145
+
96
146
  ## Documentation
97
147
 
98
148
  - [Core SDK quickstart](https://docs.fanfare.io/sdk/core/quickstart)
@@ -1 +1 @@
1
- function n(n){switch(n){case"NOT_BID":return"not_bid";case"BIDDING":return"bidding";case"WINNING":return"winning";case"OUTBID":return"outbid";case"WON":return"won";case"LOST":return"lost"}}function t(n){return"won"===n||"lost"===n}function r(n){return"won"===n.status&&!0===n.degraded&&!n.admissionGrant}export{r as isGrantPendingWin,t as isTerminalAuctionStatus,n as toAuctionConsumerStatus};
1
+ function n(n){switch(n){case"NOT_BID":return"not_bid";case"WINNING":return"winning";case"OUTBID":return"outbid";case"WON":return"won";case"LOST":return"lost"}}function t(n){return"won"===n||"lost"===n}function r(n){return"won"===n.status&&!0===n.degraded&&!n.admissionGrant}export{r as isGrantPendingWin,t as isTerminalAuctionStatus,n as toAuctionConsumerStatus};
@@ -1 +1 @@
1
- import{AuctionDistributionResults as t}from"@fanfare-io/fanfare-sdk-contracts";import{createError as i}from"../core/errors.js";import{isMoneyString as e,addMoney as n}from"../core/money.js";import{buildGrantPaymentAffordance as s}from"../experiences/sequence-driver.js";import{isGrantPendingWin as o}from"./auction-status.js";import{isUnresolvedAuctionConsumer as a}from"./auction.sequence.js";function r(t){return!t||("lost"===t.type||"closed"===t.type)&&!t.reason}function c(t){return new p(t)}function u(e){const{distribution:n,consumer:s,grant:o,settlement:c,selection:u,outcome:d}=e;if(!n)return{phase:"unavailable"};const p=function(t){if("auction"!==t.type)throw i.validationError("Auction driver received a non-auction distribution");return t}(n),l=s&&"auction"===s.mechanism?function(t){const{mechanism:i,...e}=t;return e}(s):m(n);if(o)return"completed"===d?.type?h(p,l,d):o.expiresAt&&Date.now()>new Date(o.expiresAt).getTime()?"open"===p.lifecycle?{phase:"enterable",mechanism:"auction",distribution:p,consumer:m(p)}:h(p,l,{type:"expired"}):c&&u?{phase:"granted",mechanism:"auction",distribution:p,consumer:l,grant:o,settlement:c,selection:u}:{phase:"unavailable"};if("closed"===p.lifecycle){if("not_bid"===l.status&&p.result===t.FLOOR_UNSOLD&&r(d))return h(p,l,{type:"lost",reason:t.FLOOR_UNSOLD});if("auction"===s?.mechanism&&!d)return h(p,l,{type:"lost"})}const b=d??function(t){return"lost"===t.status?{type:"lost"}:void 0}(l);return b?h(p,l,b):"settling"===p.lifecycle?{phase:"settling",mechanism:"auction",distribution:p,consumer:l}:a(l)?{phase:"participating",mechanism:"auction",distribution:p,consumer:l}:"scheduled"===p.lifecycle?{phase:"scheduled",mechanism:"auction",distribution:p}:"open"===p.lifecycle?{phase:"enterable",mechanism:"auction",distribution:p,consumer:l}:h(p,l,{type:"closed"})}function d(t,i,e){return t&&"auction"===t.mechanism&&i?"won"===i.type?{...t,status:"won"}:!e?.finalize&&"not_bid"===t.status&&r(i)?t:{...t,status:"lost"}:t}class p{constructor(t){this.deps=t,this.mechanism="auction"}project(t){return u(t)}buildView(t,e){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const n=function(t){if(!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("Auction driver received a non-auction sequence");return t}(t);switch(n.phase){case"scheduled":return{phase:"scheduled",mechanism:"auction",distribution:n.distribution,startsAt:n.distribution.startsAt??n.distribution.opensAt};case"enterable":return{phase:"enterable",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,bid:t=>this.bid(t)};case"participating":return{phase:"participating",mechanism:"auction",consumer:n.consumer,display$:e.displayAtom,bid:t=>this.bid(t)};case"settling":return{phase:"settling",mechanism:"auction",distribution:n.distribution,consumer:n.consumer};case"granted":{const t={phase:"granted",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,grant:n.grant,selection:n.selection},{settlement:i}=n;if("internal"===i.processor&&"at_end"===i.mode){const o=s(i,n.pendingAction,e);return o?{...t,settlement:i,claim:()=>e.claim(),payment:o}:{...t,settlement:i,claim:()=>e.claim()}}return"external"===i.processor?{...t,settlement:i,claim:()=>e.claim()}:{...t,settlement:i}}case"ended":return{phase:"ended",mechanism:"auction",consumer:n.consumer,outcome:n.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 enter(){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation()})}async enterWithinOperation(){const t=this.currentAuctionSequence("enterable","No open auction to enter").distribution;await this.deps.auctions.enter(t.id,void 0),await this.deps.auctions.status(t.id);const i={mechanism:"auction",status:"not_bid",id:e=t.id,distributionId:e};var e;this.deps.ensureDisplayAtom({id:t.id,type:"auction"}),this.deps.commit(u({distribution:t,consumer:i})),this.deps.emitEvent("sequence_change","success","user","Entered auction",{distributionId:t.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const t=this.currentAuctionSequence("participating"),i=t.consumer.distributionId??t.consumer.id??t.distribution.id;await this.deps.auctions.leave(i),this.deps.stopRuntimeMonitoring(!0),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"auction"})}async bid(t){await this.deps.runSerializedOperation(async()=>{const i=this.currentAuctionSequenceForBid(),s=i.consumer.distributionId??i.consumer.id??i.distribution.id,a=await this.deps.auctions.bid(s,t);if("won"===a.status||"lost"===a.status){const t=this.deps.getCurrentSequence();return void(t&&("enterable"===t.phase||"participating"===t.phase||"settling"===t.phase)&&"mechanism"in t&&"auction"===t.mechanism&&t.distribution.id===s&&("won"===a.status&&a.admissionGrant?this.deps.applyMonitorUpdate({type:"granted",token:a.admissionGrant}):o(a)||this.deps.applyMonitorUpdate({type:"ended",outcome:{type:a.status}})))}const r=function(t){switch(t){case"not_bid":case"bidding":case"winning":case"outbid":return t;case"won":case"lost":return}}(a.status);if(void 0===r)return;const c="auction"===i.distribution.details?.type?i.distribution.details:void 0,d=c?.bidIncrement,p=a.highestBid&&d&&e(a.highestBid)&&e(d)?n(a.highestBid,d):void 0,m=this.deps.getDisplayState(),h="auction"===m?.type?m.settleAt:void 0,l=this.deps.ensureDisplayAtom({id:s,type:"auction"});"enterable"===i.phase&&this.deps.commit(u({distribution:i.distribution,consumer:{mechanism:"auction",status:r,id:s,distributionId:s,currentBid:t,highestBid:a.highestBid}})),l.set({type:"auction",currentBid:t,highestBid:a.highestBid,bidCount:a.bidCount,status:r,currencyCode:c?.currencyCode,bidIncrement:d,minNextBid:p,closeAt:c?.closeAt??c?.endsAt,settleAt:h})})}currentAuctionSequence(t,e="Not participating in an auction"){const n=this.deps.getCurrentSequence();if(!n||n.phase!==t||!("mechanism"in n)||"auction"!==n.mechanism)throw i.validationError(e);return n}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 m(t){return{status:"not_bid",distributionId:t.id}}function h(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{c as createAuctionDriver,r as isUpgradeableGenericLoss,u as projectAuctionSequence,d as withAuctionTerminalConsumer};
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 +1 @@
1
- import{AuctionBidderStateResponseSchema as t,AuctionHighestBidResponseSchema as i,AuctionBidHistoryResponseSchema as e}from"@fanfare-io/fanfare-sdk-contracts/auction";import{createError as s}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{isMoneyString as a,addMoney as o,isMoneyGreaterThan as r,normalizeMoney as d,isMoneyLessThanOrEqualTo as u,subtractMoney as h}from"../core/money.js";import{parseResponse as c}from"../core/parse-response.js";import{createPollScheduler as l}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as g}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as p}from"../state/events.js";import{getSDKStore as m}from"../state/store.js";import{isGrantPendingWin as b,isTerminalAuctionStatus as y,toAuctionConsumerStatus as f}from"./auction-status.js";class B{constructor(t){this.logger=n(),this.events=p(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.pollSchedulers=/* @__PURE__ */new Map,this.lastStatusCache=/* @__PURE__ */new Map,this.hasSuccessfulReadThisSession=/* @__PURE__ */new Map,this.lastSettleAtMs=/* @__PURE__ */new Map,this.visibilityHandlers=/* @__PURE__ */new Map,this.autoRebidConfigs=/* @__PURE__ */new Map,this.monitorRuntime=new g,this.bidHistoryCache=/* @__PURE__ */new Map,this.auctionDetailsCache=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.BID_HISTORY_CACHE_TTL=5e3,this.AUCTION_DETAILS_CACHE_TTL=3e4,this.NO_BID_END_GRACE_MS=5e3,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return m()}computeMinNextBid(t,i){if(i&&a(t)&&a(i))return o(t,i)}async getAuctionDetailsCached(t){const i=this.auctionDetailsCache.get(t);if(i&&Date.now()-i.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL)return i.details;const e=await this.http.get(`/auctions/${t}`);return this.auctionDetailsCache.set(t,{details:e,fetchedAt:Date.now()}),e}getTrackedParticipation(t){return this.trackedParticipations.get(t)}getAuctionCloseAt(t){return t.closeAt??t.settleAt}getAuctionSettleAt(t){return t.settleAt??t.closeAt}buildDisplayState(t,{status:i,highestBid:e,currentBid:s,bidCount:n}){return{highestBid:e,currentBid:s,bidCount:n,closeAt:this.getAuctionCloseAt(t),settleAt:t.settleAt,status:i,currencyCode:t.currencyCode,minNextBid:e?this.computeMinNextBid(e,t.minBidIncrement):void 0,bidIncrement:t.minBidIncrement??void 0}}emitTransitionEvents(t){for(const i of t)this.events.emit(i.type,i.payload)}applyTransition(t,i){void 0!==i.trackedParticipation&&(i.trackedParticipation?this.trackedParticipations.set(t,i.trackedParticipation):this.trackedParticipations.delete(t));const e=this.monitorRuntime.getDisplayAtom(t);e&&i.displayState&&e.set({type:"auction",...i.displayState}),this.emitTransitionEvents(i.events),i.monitorUpdate&&this.monitorRuntime.notify(t,i.monitorUpdate),i.stopWatching&&this.stopWatching(t),i.autoRebidStatus&&this.handleAutoRebid(t,i.autoRebidStatus)}checkOutbidConditions(t,i,e){if("outbid"!==i.status)return!1;if("winning"===t.status&&"outbid"===i.status)return!0;if(e?.currentBid&&i.highestBid){const s=e.currentBid,n=i.highestBid,o=t.highestBid||"0";if(a(s)&&a(n)&&a(o))return r(n,o)&&r(n,s)&&"outbid"===i.status}return"outbid"!==t.status&&"outbid"===i.status}buildBidTransition(t,i,e,s,n,a){const o={type:"auction:bid-placed",payload:{auctionId:t,amount:e,status:i.status}};if("won"===i.status&&!b(i)||"lost"===i.status){const a=this.buildTerminalTransition(t,i.status,s,{currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,admissionGrant:n});return{...a,events:[o,...a.events]}}return{trackedParticipation:{auctionId:t,enteredAt:a?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,lastBidAt:/* @__PURE__ */(new Date).toISOString(),metadata:a?.metadata,fingerprint:a?.fingerprint},displayState:this.buildDisplayState(s,{status:i.status,highestBid:i.highestBid,currentBid:e,bidCount:i.bidCount}),events:[o,..."winning"===i.status?[{type:"auction:winning",payload:{auctionId:t,amount:e,highestBid:i.highestBid}}]:[],..."outbid"===i.status?[{type:"auction:outbid",payload:{auctionId:t,yourBid:e,highestBid:i.highestBid}}]:[]],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null}}buildTerminalTransition(t,i,e,s){return"won"===i?{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:i,highestBid:s.highestBid,currentBid:s.currentBid,bidCount:s.bidCount}),events:[{type:"auction:won",payload:{auctionId:t,winningBid:s.currentBid??"",admissionGrant:s.admissionGrant}}],monitorUpdate:s.admissionGrant?{type:"granted",token:s.admissionGrant}:{type:"ended",outcome:{type:"won",distributionType:"auction",distributionId:t}},stopWatching:!0,autoRebidStatus:null}:{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:i,highestBid:s.highestBid,currentBid:s.currentBid,bidCount:s.bidCount}),events:[{type:"auction:lost",payload:{auctionId:t,highestBid:s.highestBid}}],monitorUpdate:{type:"ended",outcome:{type:"lost"}},stopWatching:!0,autoRebidStatus:null}}buildStatusTransition(t,i,e,{previousParticipation:s,previousStatus:n}){const a=i.currentBid??s?.currentBid,o=i.bidCount??s?.bidCount;if(y(n?.status))return{displayState:null,events:[],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null};if("won"===i.status||"lost"===i.status)return this.buildTerminalTransition(t,i.status,e,{currentBid:a,highestBid:i.highestBid,bidCount:o,admissionGrant:i.admissionGrant});const r=e.closeAt?new Date(e.closeAt).getTime():void 0;if("not_bid"===i.status&&void 0!==r&&Date.now()>=r+this.NO_BID_END_GRACE_MS)return{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:"ended",highestBid:i.highestBid,currentBid:a,bidCount:o}),events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"ended"}},stopWatching:!0,autoRebidStatus:null};const d={auctionId:t,enteredAt:s?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:a,highestBid:i.highestBid,bidCount:o,metadata:s?.metadata,fingerprint:s?.fingerprint},u=[];let h=null;return n&&(this.checkOutbidConditions(n,i,s)&&(u.push({type:"auction:outbid",payload:{auctionId:t,yourBid:n.currentBid||s?.currentBid||"",highestBid:i.highestBid}}),h=i),"winning"!==n.status&&"winning"===i.status&&u.push({type:"auction:winning",payload:{auctionId:t,amount:a||"",highestBid:i.highestBid}}),n.highestBid!==i.highestBid&&"winning"!==i.status&&u.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:d,displayState:this.buildDisplayState(e,{status:i.status,highestBid:i.highestBid,currentBid:a,bidCount:o}),events:u,monitorUpdate:null,stopWatching:!1,autoRebidStatus:h}}buildMissingTransition(){return{trackedParticipation:null,displayState:{status:"lost"},events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"not_participating"}},stopWatching:!0,autoRebidStatus:null}}async get(t){try{return await this.getAuctionDetailsCached(t)}catch(i){throw this.logger.error("Failed to get auction",{auctionId:t,error:i}),i}}async bid(i,e,n,o){try{const r=this.lifecycleGeneration;if(!a(e))throw s.validationError("Invalid bid amount format. Must be a numeric string (e.g., '100.00')");if(!this.store.session)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");let d=this.bumpOperationGeneration(i),u=this.getTrackedParticipation(i);u||(this.logger.warn("Attempting to bid without entering auction first",{auctionId:i}),await this.enter(i,void 0,n,o),u=this.getTrackedParticipation(i),d=this.bumpOperationGeneration(i)),this.logger.info("Placing bid",{auctionId:i,amount:e});const h=`/auctions/${i}/bid`,l=await this.getAuctionDetailsCached(i),g=await this.http.post(h,{amount:e},n),p=c(t,g,{endpoint:h},{throwOnFailure:!0});if(!p.ok)throw p.error;const m=p.value;m.degraded&&this.logger.warn("Bid committed; the server deferred a post-commit effect to replay",{auctionId:i});const B=await this.proveHighestBid(i,m,e),S=f(m.status),v={status:S,amount:e,highestBid:B,bidCount:m.bidCount??(u?.bidCount||0)+1,admissionGrant:m.admissionGrant,...m.degraded?{degraded:!0}:{}};if(!this.canApplyAsyncResult(i,r,void 0,d))return v;if(this.applyTransition(i,this.buildBidTransition(i,v,e,l,m.admissionGrant,u)),b(v))return v;const w=this.lastStatusCache.get(i);return y(w?.status)||this.lastStatusCache.set(i,{auctionId:i,status:S,currentBid:e,highestBid:B,timeRemaining:0,bidCount:v.bidCount,admissionGrant:m.admissionGrant}),v}catch(r){throw this.logger.error("Failed to place bid",{auctionId:i,amount:e,error:r}),this.events.emit("auction:error",{auctionId:i,error:r}),r}}async proveHighestBid(t,e,s){const n=`/auctions/${t}/highest-bid`,a=d(e.winningBidAmount??e.lastBidAmount??s);try{const t=await this.http.get(n),e=c(i,t,{endpoint:n});return e.ok?d(e.value.amount):a}catch(o){return this.logger.warn("Highest-bid read failed after a committed bid; reporting the committed amount",{auctionId:t,error:o}),a}}async enter(t,i,e,n){try{const a=this.lifecycleGeneration,o=this.store.session;if(!o)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const r=this.bumpOperationGeneration(t);if(this.logger.info("Entering auction",{auctionId:t}),await this.http.post(`/auctions/${t}/enter`,{metadata:i},e),!this.canApplyAsyncResult(t,a,void 0,r))return;this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"not_bid",metadata:i,fingerprint:o.deviceFingerprint}),this.lastStatusCache.delete(t),this.emitTransitionEvents([{type:"auction:entered",payload:{auctionId:t}},{type:"experience:distribution-entered",payload:{distributionType:"auction",distributionId:t,...n}}])}catch(a){throw this.logger.error("Failed to enter auction",{auctionId:t,error:a}),this.events.emit("auction:error",{auctionId:t,error:a}),a}}async leave(t,i){const e=this.getTrackedParticipation(t);if(e&&(void 0!==e.currentBid||"not_bid"!==e.status))throw s.cannotLeaveAfterBid(t);try{const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);this.logger.info("Leaving auction",{auctionId:t});try{await this.http.post(`/auctions/${t}/leave`,void 0,i)}catch(n){if(404!==n?.status)throw n;this.logger.debug("Leave endpoint not available",{auctionId:t})}if(!this.canApplyAsyncResult(t,e,void 0,s))return;this.stopMonitoring(t),this.trackedParticipations.delete(t),this.emitTransitionEvents([{type:"auction:left",payload:{auctionId:t}}])}catch(n){throw this.logger.error("Failed to leave auction",{auctionId:t,error:n}),this.events.emit("auction:error",{auctionId:t,error:n}),n}}async status(t){const i=this.inFlightRequests.get(t);if(i)return this.logger.debug("Returning existing in-flight request",{auctionId:t}),i;const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),n=this.doStatus(t,e,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,n),n}async doStatus(e,s,n,a){try{const r=`/auctions/${e}/status`,u=`/auctions/${e}/highest-bid`,[h,l,g]=await Promise.allSettled([this.http.get(r),this.http.get(u),this.getAuctionDetailsCached(e)]);if("rejected"===h.status){if("object"==typeof(o=h.reason)&&null!==o&&"status"in o&&404===o.status)return this.applyMissingStatus(e,s,n,a);throw h.reason}if("rejected"===l.status)throw l.reason;if("rejected"===g.status)throw g.reason;const p=h.value,m=l.value,b=g.value,B=c(t,p,{endpoint:r},{throwOnFailure:!0});if(!B.ok)throw B.error;const S=c(i,m,{endpoint:u},{throwOnFailure:!0});if(!S.ok)throw S.error;const v=B.value,w=S.value,C=this.getAuctionSettleAt(b),A=C?new Date(C).getTime():Number.NaN,I=d(w.amount),R=this.getTrackedParticipation(e),T=v.lastBidAmount??v.winningBidAmount,G={auctionId:e,status:f(v.status),currentBid:R?.currentBid??(T?d(T):void 0),highestBid:I,timeRemaining:Number.isFinite(A)?Math.max(0,A-Date.now()):0,bidCount:v.bidCount,admissionGrant:v.admissionGrant};if(!this.canApplyAsyncResult(e,s,n,a))return G;const M=this.lastStatusCache.get(e),k=this.buildStatusTransition(e,G,b,{previousParticipation:R,previousStatus:M});return this.applyTransition(e,k),this.hasSuccessfulReadThisSession.set(e,!0),Number.isFinite(A)?this.lastSettleAtMs.set(e,A):this.lastSettleAtMs.delete(e),y(M?.status)?this.lastStatusCache.set(e,M):y(G.status)?this.lastStatusCache.set(e,G):k.stopWatching?this.lastStatusCache.delete(e):this.lastStatusCache.set(e,G),this.events.emit("auction:status-updated",{auctionId:e,status:G}),G}catch(o){if(!this.canApplyAsyncResult(e,s,n,a))return this.lastStatusCache.get(e)??{auctionId:e,status:"not_bid",highestBid:"0.00",timeRemaining:0};throw this.logger.error("Failed to get auction status",{auctionId:e,error:o}),this.events.emit("auction:error",{auctionId:e,error:o}),o}var o}applyMissingStatus(t,i,e,s){const n={auctionId:t,status:"not_bid",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(t,i,e,s))return n;const a=this.lastStatusCache.get(t);if(!y(a?.status)){const i=this.buildMissingTransition();this.applyTransition(t,i),this.lastStatusCache.delete(t)}return this.hasSuccessfulReadThisSession.set(t,!0),this.lastSettleAtMs.delete(t),this.events.emit("auction:status-updated",{auctionId:t,status:n}),n}async getBidHistory(t){try{const i=this.bidHistoryCache.get(t);if(i&&Date.now()-i.fetchedAt<this.BID_HISTORY_CACHE_TTL)return this.logger.debug("Returning cached bid history",{auctionId:t}),i.bids;this.logger.info("Fetching bid history",{auctionId:t});const s=`/auctions/${t}/bids/history`,n=await this.http.get(s),a=c(e,n,{endpoint:s},{throwOnFailure:!0});if(!a.ok)throw a.error;const o=a.value.map(t=>({amount:d(t.amount),timestamp:t.timestamp,isWinning:t.isHighest,isYours:!0}));return this.bidHistoryCache.set(t,{bids:o,fetchedAt:Date.now()}),this.events.emit("auction:history-fetched",{auctionId:t,bidCount:o.length}),o}catch(i){throw this.logger.error("Failed to get bid history",{auctionId:t,error:i}),this.events.emit("auction:error",{auctionId:t,error:i}),i}}startWatching(t,i=5e3,e){this.stopWatching(t);const s=this.lifecycleGeneration,n=this.bumpMonitorGeneration(t);this.hasSuccessfulReadThisSession.set(t,!1),this.lastStatusCache.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Starting auction status polling",{auctionId:t,intervalMs:i});const a=l({baseMs:i,suppressBackoff:()=>this.shouldSuppressBackoff(t),onDegradedChange:e,shouldContinue:()=>this.pollSchedulers.has(t)&&this.canApplyAsyncResult(t,s,n),run:async()=>{const i=this.getOperationGeneration(t);try{await this.doStatus(t,s,n,i)}catch(e){if(!this.canApplyAsyncResult(t,s,n,i))return;throw this.logger.error("Polling failed",{auctionId:t,error:e}),e}}});this.pollSchedulers.set(t,a),this.registerVisibilityPoke(t,a),a.start(),this.events.emit("auction:watching-started",{auctionId:t,intervalMs:i})}stopWatching(t){this.bumpMonitorGeneration(t);const i=this.pollSchedulers.get(t);i&&(i.stop(),this.pollSchedulers.delete(t),this.unregisterVisibilityPoke(t),this.lastStatusCache.delete(t),this.hasSuccessfulReadThisSession.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Stopped watching auction",{auctionId:t}),this.events.emit("auction:watching-stopped",{auctionId:t}))}stopAllWatching(){for(const t of Array.from(this.pollSchedulers.keys()))this.stopWatching(t)}shouldSuppressBackoff(t){return!this.hasSuccessfulReadThisSession.get(t)||this.isInSettlingWindow(t)}isInSettlingWindow(t){const i=this.lastSettleAtMs.get(t);return void 0!==i&&(Date.now()>=i-12e4&&!y(this.lastStatusCache.get(t)?.status))}registerVisibilityPoke(t,i){if("undefined"==typeof document)return;const e=()=>{"visible"===document.visibilityState&&this.isInSettlingWindow(t)&&i.poke()};document.addEventListener("visibilitychange",e),this.visibilityHandlers.set(t,e)}unregisterVisibilityPoke(t){if("undefined"==typeof document)return void this.visibilityHandlers.delete(t);const i=this.visibilityHandlers.get(t);i&&(document.removeEventListener("visibilitychange",i),this.visibilityHandlers.delete(t))}bumpMonitorGeneration(t){const i=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,i),i}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const i=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,i),i}canApplyAsyncResult(t,i,e,s){return!this.destroyed&&this.lifecycleGeneration===i&&((void 0===e||this.monitorGenerations.get(t)===e)&&(void 0===s||this.getOperationGeneration(t)===s))}enableAutoRebid(t,i,e){if(!a(i)||!a(e))throw s.validationError("Invalid monetary format. Must be numeric strings (e.g., '100.00')");if(u(i,"0")||u(e,"0"))throw s.validationError("Max bid and increment must be positive");if(r(e,i))throw s.validationError("Increment cannot be greater than max bid");const n=this.getTrackedParticipation(t);if(!n)throw s.validationError("Must enter auction before enabling auto-rebid");const o=d(n.currentBid),c=h(i,o);this.autoRebidConfigs.set(t,{enabled:!0,maxBid:i,increment:e,remainingBudget:c,rebidCount:0}),this.logger.info("Auto-rebid enabled",{auctionId:t,maxBid:i,increment:e,remainingBudget:c}),this.events.emit("auction:auto-rebid-enabled",{auctionId:t,maxBid:i,increment:e})}disableAutoRebid(t){const i=this.autoRebidConfigs.get(t);i&&(i.enabled=!1,this.logger.info("Auto-rebid disabled",{auctionId:t}),this.events.emit("auction:auto-rebid-disabled",{auctionId:t,finalRebidCount:i.rebidCount||0}))}getAutoRebidConfig(t){return this.autoRebidConfigs.get(t)}async handleAutoRebid(t,i){const e=this.autoRebidConfigs.get(t);if(e&&e.enabled)try{if(!a(i.highestBid)||!a(e.maxBid)||!a(e.increment))return void this.logger.error("Invalid auto-rebid amounts",{auctionId:t,config:e});const s=o(i.highestBid,e.increment);if(r(s,e.maxBid))return this.logger.info("Auto-rebid would exceed max bid, disabling",{auctionId:t,wouldBid:s,maxBid:e.maxBid}),this.disableAutoRebid(t),void this.events.emit("auction:auto-rebid-max-reached",{auctionId:t,maxBid:e.maxBid,wouldNeedToBid:s});this.logger.info("Placing auto-rebid",{auctionId:t,amount:s});const n=await this.bid(t,s);e.lastRebidAt=/* @__PURE__ */(new Date).toISOString(),e.rebidCount=(e.rebidCount||0)+1,e.remainingBudget=h(e.maxBid,s),this.events.emit("auction:auto-rebid-placed",{auctionId:t,amount:s,remainingBudget:e.remainingBudget,status:n.status})}catch(s){this.logger.error("Auto-rebid failed",{auctionId:t,error:s}),this.events.emit("auction:auto-rebid-failed",{auctionId:t,error:s})}}startMonitoring(t,i,e,s){y(this.lastStatusCache.get(t)?.status)||(this.monitorRuntime.start(t,i,e),this.trackedParticipations.has(t)||this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"not_bid"}),this.startWatching(t,5e3,s))}stopMonitoring(t){this.stopWatching(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.stopAllWatching(),this.inFlightRequests.clear(),this.lastStatusCache.clear(),this.hasSuccessfulReadThisSession.clear(),this.lastSettleAtMs.clear(),this.visibilityHandlers.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.bidHistoryCache.clear(),this.auctionDetailsCache.clear(),this.autoRebidConfigs.clear(),this.monitorRuntime.clear()}}export{B as AuctionManagementModule};
1
+ import{AuctionDetailsResponseSchema as t,AuctionBidderStateResponseSchema as i}from"@fanfare-io/fanfare-sdk-contracts/auction";import*as e from"valibot";import{createError as s}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{isMoneyString as a,isMoneyGreaterThan as o,normalizeMoney as r}from"../core/money.js";import{parseResponse as d}from"../core/parse-response.js";import{createPollScheduler as c}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as u}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as l}from"../state/events.js";import{getSDKStore as h}from"../state/store.js";import{isGrantPendingWin as p,toAuctionConsumerStatus as g,isTerminalAuctionStatus as m}from"./auction-status.js";import{assertBidInputMatchesMechanism as y}from"./auction.driver.js";import{advanceDutchPricing as S}from"./dutch-pricing.js";const f=/* @__PURE__ */Symbol("auction-participation-missing");function v(t){return"won"===t.status&&!t.admissionGrant&&!t.outcome}function b(t){return void 0!==t&&m(t.status)&&!v(t)}function A(t){return"english"===t.auctionType?r(t.highestBid):r(t.currentPrice)}const w=e.object({productId:e.string(),variantId:e.optional(e.string())});class D{constructor(t){this.logger=n(),this.events=l(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.pollSchedulers=/* @__PURE__ */new Map,this.pollBaseMs=/* @__PURE__ */new Map,this.degradedHandlers=/* @__PURE__ */new Map,this.lastStatusCache=/* @__PURE__ */new Map,this.hasSuccessfulReadThisSession=/* @__PURE__ */new Map,this.lastSettleAtMs=/* @__PURE__ */new Map,this.visibilityHandlers=/* @__PURE__ */new Map,this.dutchStepTimers=/* @__PURE__ */new Map,this.monitorRuntime=new u,this.auctionDetailsCache=/* @__PURE__ */new Map,this.watchDetails=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.AUCTION_DETAILS_CACHE_TTL=3e4,this.WATCHING_POLL_MS=15e3,this.PARTICIPATING_POLL_MS=5e3,this.NO_BID_END_GRACE_MS=5e3,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return h()}async getAuctionDetailsCached(i,e){const s=this.auctionDetailsCache.get(i),n=void 0!==s&&Date.now()-s.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL,a=void 0!==s&&"error"in s;if(s&&n&&(!e?.bypassCache||a)){if(void 0!==s.details)return s.details;if("error"in s)throw s.error}try{const e=await this.http.get(`/auctions/${i}`),s=d(t,e,{endpoint:`/auctions/${i}`},{throwOnFailure:!0});if(!s.ok)throw s.error;const n=s.value;return this.auctionDetailsCache.set(i,{details:n,fetchedAt:Date.now()}),n}catch(o){if(this.auctionDetailsCache.set(i,{...void 0!==s?.details?{details:s.details}:{},fetchedAt:Date.now(),error:o}),void 0!==s?.details)return s.details;throw o}}getTrackedParticipation(t){return this.trackedParticipations.get(t)}buildPricing(t,i){return"english"===t.auctionType?{model:"english",...null===t.highestBid?{}:{highestBid:t.highestBid},minNextBid:t.minNextBid,...i?.minBidIncrement?{bidIncrement:i.minBidIncrement}:{},bidCount:t.bidCount,effectiveSettleAt:t.effectiveSettleAt,autoExtended:t.autoExtended,reserveMet:t.reserveMet,ended:t.ended}:{model:"dutch",currentPrice:t.currentPrice,remainingQuantity:t.remainingQuantity,totalQuantity:t.totalQuantity,...null===t.nextDropAt?{}:{nextDropAt:t.nextDropAt},...null===t.floorPrice?{}:{floorPrice:t.floorPrice},settleAt:t.settleAt,...i?.startPrice?{startPrice:i.startPrice}:{},...i?.priceDropAmount?{priceDropAmount:i.priceDropAmount}:{},..."number"==typeof i?.priceDropIntervalSeconds?{priceDropIntervalSeconds:i.priceDropIntervalSeconds}:{},ended:t.ended}}buildDisplayState(t,i,e){const s=("english"===i.auctionType?i.closeAt:null)??e?.closeAt??void 0;return{status:t,...e?.currencyCode?{currencyCode:e.currencyCode}:{},...s?{closeAt:s}:{},pricing:this.buildPricing(i,e)}}emitTransitionEvents(t){for(const i of t)this.events.emit(i.type,i.payload)}applyTransition(t,i){void 0!==i.trackedParticipation&&(i.trackedParticipation?this.trackedParticipations.set(t,i.trackedParticipation):this.trackedParticipations.delete(t));const e=this.monitorRuntime.getDisplayAtom(t);e&&i.displayState&&e.set({type:"auction",...i.displayState}),this.emitTransitionEvents(i.events),i.monitorUpdate&&this.monitorRuntime.notify(t,i.monitorUpdate),i.stopWatching?this.stopWatching(t):i.displayState&&this.armDutchStep(t)}armDutchStep(t){this.clearDutchStep(t);const i=this.monitorRuntime.getDisplayAtom(t),e=i?.get();if(!i||"auction"!==e?.type)return;const s=e.pricing;if("dutch"!==s?.model||s.ended||!s.nextDropAt||void 0===s.priceDropAmount||void 0===s.priceDropIntervalSeconds)return;const n=Date.parse(s.nextDropAt);if(Number.isNaN(n))return;const a=Math.max(0,n-Date.now());if(a>2147483647)return;const o=setTimeout(()=>{this.dutchStepTimers.delete(t);const i=this.monitorRuntime.getDisplayAtom(t),e=i?.get();if(!i||"auction"!==e?.type||"dutch"!==e.pricing?.model)return;const s=S(e.pricing,Date.now());s!==e.pricing&&(i.set({...e,pricing:s}),this.armDutchStep(t))},a+1);this.dutchStepTimers.set(t,o)}clearDutchStep(t){const i=this.dutchStepTimers.get(t);void 0!==i&&(clearTimeout(i),this.dutchStepTimers.delete(t))}checkOutbidConditions(t,i,e){if("outbid"!==i.status)return!1;if("winning"===t.status&&"outbid"===i.status)return!0;if(e?.currentBid&&i.highestBid){const s=e.currentBid,n=i.highestBid,r=t.highestBid||"0";if(a(s)&&a(n)&&a(r))return o(n,r)&&o(n,s)&&"outbid"===i.status}return"outbid"!==t.status&&"outbid"===i.status}buildBidTransition(t,i,e,s,n){const a=i.amount,o=n.isFirstBid?[{type:"auction:entered",payload:{auctionId:t}},{type:"experience:distribution-entered",payload:{distributionType:"auction",distributionId:t,...n.attribution}}]:[],r={type:"auction:bid-placed",payload:{auctionId:t,amount:a,status:i.status}};if("won"===i.status&&!p(i)||"lost"===i.status){const s=this.buildTerminalTransition(t,i.status,i.live,e,{currentBid:a,admissionGrant:i.admissionGrant});return{...s,events:[...o,r,...s.events]}}return{trackedParticipation:{auctionId:t,enteredAt:s?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:a,highestBid:i.highestBid,bidCount:i.bidCount,lastBidAt:/* @__PURE__ */(new Date).toISOString(),metadata:s?.metadata,fingerprint:s?.fingerprint},displayState:this.buildDisplayState(i.status,i.live,e),events:[...o,r,..."winning"===i.status?[{type:"auction:winning",payload:{auctionId:t,amount:a,highestBid:i.highestBid}}]:[],..."outbid"===i.status?[{type:"auction:outbid",payload:{auctionId:t,yourBid:a,highestBid:i.highestBid}}]:[]],monitorUpdate:null,stopWatching:!1}}buildTerminalTransition(t,i,e,s,n){return"won"===i?{trackedParticipation:null,displayState:this.buildDisplayState(i,e,s),events:[{type:"auction:won",payload:{auctionId:t,winningBid:n.currentBid??"",admissionGrant:n.admissionGrant}}],monitorUpdate:n.admissionGrant?{type:"granted",token:n.admissionGrant}:{type:"resync"},stopWatching:!0}:{trackedParticipation:null,displayState:this.buildDisplayState(i,e,s),events:[{type:"auction:lost",payload:{auctionId:t,highestBid:A(e)}}],monitorUpdate:{type:"ended",outcome:{type:"lost"}},stopWatching:!0}}buildStatusTransition(t,i,e,s,{previousParticipation:n,previousStatus:a}){const o=i.currentBid??n?.currentBid,r=i.bidCount??n?.bidCount;if(b(a))return{displayState:null,events:[],monitorUpdate:null,stopWatching:!1};if("won"===i.status&&!v(i)||"lost"===i.status)return this.buildTerminalTransition(t,i.status,e,s,{currentBid:o,admissionGrant:i.admissionGrant});const d=("english"===e.auctionType?e.closeAt:e.settleAt)??s?.closeAt,c=d?new Date(d).getTime():void 0,u=void 0!==c&&Date.now()>=c+this.NO_BID_END_GRACE_MS;if("not_bid"===i.status&&(e.ended||u))return{trackedParticipation:null,displayState:this.buildDisplayState("ended",e,s),events:[],monitorUpdate:{type:"ended",outcome:{type:"closed"}},stopWatching:!0};const l={auctionId:t,enteredAt:n?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:o,highestBid:i.highestBid,bidCount:r,metadata:n?.metadata,fingerprint:n?.fingerprint},h=[];return a&&(this.checkOutbidConditions(a,i,n)&&h.push({type:"auction:outbid",payload:{auctionId:t,yourBid:a.currentBid||n?.currentBid||"",highestBid:i.highestBid}}),"winning"!==a.status&&"winning"===i.status&&h.push({type:"auction:winning",payload:{auctionId:t,amount:o||"",highestBid:i.highestBid}}),a.highestBid!==i.highestBid&&"winning"!==i.status&&h.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:l,displayState:this.buildDisplayState(i.status,e,s),events:h,monitorUpdate:null,stopWatching:!1}}buildMissingTransition(){return{trackedParticipation:null,displayState:{status:"lost"},events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"not_participating"}},stopWatching:!0}}async get(t){try{return await this.getAuctionDetailsCached(t)}catch(i){throw this.logger.error("Failed to get auction",{auctionId:t,error:i}),i}}async bid(t,n,o,c){try{const u=this.lifecycleGeneration;if(void 0!==n.amount&&!a(n.amount))throw s.validationError("Invalid bid amount format. Must be a numeric string (e.g., '100.00')");if(!this.store.session)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const l=this.bumpOperationGeneration(t),h=this.pollSchedulers.has(t),m=this.getTrackedParticipation(t),S=!(m&&"not_bid"!==m.status||b(this.lastStatusCache.get(t)));this.logger.info("Placing bid",{auctionId:t,amount:n.amount});const f=`/auctions/${t}/bid`,v=await this.readDetailsForDisplay(t);v&&y(n,v.auctionType);const D=await this.http.post(f,{...void 0===n.amount?{}:{amount:n.amount},...void 0===n.selection?{}:{selection:n.selection}},o),B=d(i,D,{endpoint:f},{throwOnFailure:!0});if(!B.ok)throw B.error;const C=B.value;C.degraded&&this.logger.warn("Bid committed; the credential mint did not complete on this request",{auctionId:t});const I=g(C.status),T=r(C.winningBidAmount??C.lastBidAmount??n.amount,A(C.live)),M="WON"===C.status&&void 0!==C.selection?e.safeParse(w,C.selection):void 0,P=M?.success?M.output:void 0,G={...P?{selection:P}:{},status:I,amount:T,highestBid:A(C.live),bidCount:C.bidCount??(m?.bidCount||0)+1,admissionGrant:C.admissionGrant,...C.degraded?{degraded:!0}:{},live:C.live};if(!this.canApplyAsyncResult(t,u,void 0,l))return G;if(b(this.lastStatusCache.get(t))||h&&!this.pollSchedulers.has(t))return G;if(this.applyTransition(t,this.buildBidTransition(t,G,v,m,{isFirstBid:S,attribution:c})),this.syncPollCadence(t),p(G))return G;return b(this.lastStatusCache.get(t))||this.lastStatusCache.set(t,{auctionId:t,status:I,currentBid:T,highestBid:G.highestBid,timeRemaining:0,bidCount:G.bidCount,admissionGrant:C.admissionGrant,live:C.live}),G}catch(u){throw this.logger.error("Failed to place bid",{auctionId:t,amount:n.amount,error:u}),this.events.emit("auction:error",{auctionId:t,error:u}),u}}async status(t){const i=this.inFlightRequests.get(t);if(i)return this.logger.debug("Returning existing in-flight request",{auctionId:t}),i;const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),n=this.doStatus(t,e,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,n),n}async doStatus(t,e,s,n){try{const a=`/auctions/${t}/status`,o=await this.http.get(a).catch(t=>{if(function(t){return"object"==typeof t&&null!==t&&"status"in t&&404===t.status}(t))return f;throw t});if(o===f)return this.applyMissingStatus(t,e,s,n);const c=d(i,o,{endpoint:a},{throwOnFailure:!0});if(!c.ok)throw c.error;const u=c.value,l=await this.readDetailsForDisplay(t),h=u.live,p=new Date(function(t){return"english"===t.auctionType?t.effectiveSettleAt:t.settleAt}(h)).getTime(),m=A(h),y=this.getTrackedParticipation(t),S=u.lastBidAmount??u.winningBidAmount,v={auctionId:t,status:g(u.status),currentBid:void 0!==S?r(S):y?.currentBid,highestBid:m,timeRemaining:Number.isFinite(p)?Math.max(0,p-Date.now()):0,bidCount:u.bidCount,admissionGrant:u.admissionGrant,expiresAt:u.expiresAt,degraded:u.degraded,outcome:u.outcome,selection:u.selection,live:h};if(!this.canApplyAsyncResult(t,e,s,n))return v;const w=this.lastStatusCache.get(t),D=this.buildStatusTransition(t,v,h,l,{previousParticipation:y,previousStatus:w});return this.applyTransition(t,D),this.hasSuccessfulReadThisSession.set(t,!0),Number.isFinite(p)?this.lastSettleAtMs.set(t,p):this.lastSettleAtMs.delete(t),b(w)?this.lastStatusCache.set(t,w):b(v)?this.lastStatusCache.set(t,v):D.stopWatching?this.lastStatusCache.delete(t):this.lastStatusCache.set(t,v),this.syncPollCadence(t),this.events.emit("auction:status-updated",{auctionId:t,status:v}),v}catch(a){if(!this.canApplyAsyncResult(t,e,s,n))return this.lastStatusCache.get(t)??{auctionId:t,status:"not_bid",highestBid:"0.00",timeRemaining:0};throw this.logger.error("Failed to get auction status",{auctionId:t,error:a}),this.events.emit("auction:error",{auctionId:t,error:a}),a}}async readDetailsForDisplay(t){if(this.pollSchedulers.has(t)){const i=this.watchDetails.get(t);if(i)return i}try{const i=this.auctionDetailsCache.get(t),e=Date.now(),s=void 0!==i&&!("error"in i)&&e-i.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL&&(null==i.details.openAt||e>=new Date(i.details.openAt).getTime()),n=await this.getAuctionDetailsCached(t,{bypassCache:this.pollSchedulers.has(t)&&s}),a=this.auctionDetailsCache.get(t);return this.pollSchedulers.has(t)&&void 0!==a&&!("error"in a)&&(null==n.openAt||Date.now()>=new Date(n.openAt).getTime())&&this.watchDetails.set(t,n),n}catch(i){return void this.logger.warn("Auction configuration read failed; rendering the live block alone",{auctionId:t,error:i})}}applyMissingStatus(t,i,e,s){const n={auctionId:t,status:"not_bid",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(t,i,e,s))return n;if(!b(this.lastStatusCache.get(t))){const i=this.buildMissingTransition();this.applyTransition(t,i),this.lastStatusCache.delete(t)}return this.hasSuccessfulReadThisSession.set(t,!0),this.lastSettleAtMs.delete(t),this.events.emit("auction:status-updated",{auctionId:t,status:n}),n}startWatching(t,i){this.stopWatching(t),this.hasSuccessfulReadThisSession.set(t,!1),this.lastStatusCache.delete(t),this.lastSettleAtMs.delete(t),i?this.degradedHandlers.set(t,i):this.degradedHandlers.delete(t);const e=this.desiredPollBaseMs(t);this.logger.info("Starting auction status polling",{auctionId:t,intervalMs:e}),this.installScheduler(t,e),this.events.emit("auction:watching-started",{auctionId:t,intervalMs:e})}installScheduler(t,i){const e=this.lifecycleGeneration,s=this.bumpMonitorGeneration(t),n=c({baseMs:i,suppressBackoff:()=>this.shouldSuppressBackoff(t),onDegradedChange:this.degradedHandlers.get(t),shouldContinue:()=>this.pollSchedulers.get(t)===n&&this.canApplyAsyncResult(t,e,s),run:async()=>{const i=this.getOperationGeneration(t);try{await this.doStatus(t,e,s,i)}catch(n){if(!this.canApplyAsyncResult(t,e,s,i))return;throw this.logger.error("Polling failed",{auctionId:t,error:n}),n}}});this.pollSchedulers.set(t,n),this.pollBaseMs.set(t,i),this.registerVisibilityPoke(t,n),n.start()}desiredPollBaseMs(t){const i=this.getTrackedParticipation(t)?.status??this.lastStatusCache.get(t)?.status;return(void 0===i||"not_bid"===i)&&!this.isInSettlingWindow(t)?this.WATCHING_POLL_MS:this.PARTICIPATING_POLL_MS}syncPollCadence(t){const i=this.pollSchedulers.get(t);if(!i)return;const e=this.desiredPollBaseMs(t);this.pollBaseMs.get(t)!==e&&(i.stop(),this.unregisterVisibilityPoke(t),this.logger.debug("Re-cadencing auction status polling",{auctionId:t,intervalMs:e}),this.installScheduler(t,e))}stopWatching(t){this.bumpMonitorGeneration(t),this.watchDetails.delete(t);const i=this.pollSchedulers.get(t);i&&(i.stop(),this.clearDutchStep(t),this.pollSchedulers.delete(t),this.pollBaseMs.delete(t),this.degradedHandlers.delete(t),this.unregisterVisibilityPoke(t),this.lastStatusCache.delete(t),this.hasSuccessfulReadThisSession.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Stopped watching auction",{auctionId:t}),this.events.emit("auction:watching-stopped",{auctionId:t}))}stopAllWatching(){for(const t of Array.from(this.pollSchedulers.keys()))this.stopWatching(t)}shouldSuppressBackoff(t){return!this.hasSuccessfulReadThisSession.get(t)||this.isInSettlingWindow(t)}isInSettlingWindow(t){const i=this.lastSettleAtMs.get(t);return void 0!==i&&(Date.now()>=i-12e4&&!b(this.lastStatusCache.get(t)))}registerVisibilityPoke(t,i){if("undefined"==typeof document)return;const e=()=>{"visible"===document.visibilityState&&this.isInSettlingWindow(t)&&i.poke()};document.addEventListener("visibilitychange",e),this.visibilityHandlers.set(t,e)}unregisterVisibilityPoke(t){if("undefined"==typeof document)return void this.visibilityHandlers.delete(t);const i=this.visibilityHandlers.get(t);i&&(document.removeEventListener("visibilitychange",i),this.visibilityHandlers.delete(t))}bumpMonitorGeneration(t){const i=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,i),i}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const i=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,i),i}canApplyAsyncResult(t,i,e,s){return!this.destroyed&&this.lifecycleGeneration===i&&((void 0===e||this.monitorGenerations.get(t)===e)&&(void 0===s||this.getOperationGeneration(t)===s))}startMonitoring(t,i,e,s){b(this.lastStatusCache.get(t))||(this.monitorRuntime.start(t,i,e),this.startWatching(t,s))}stopMonitoring(t){this.stopWatching(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.stopAllWatching(),this.inFlightRequests.clear(),this.lastStatusCache.clear(),this.hasSuccessfulReadThisSession.clear(),this.lastSettleAtMs.clear(),this.visibilityHandlers.clear();for(const t of Array.from(this.dutchStepTimers.keys()))this.clearDutchStep(t);this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.auctionDetailsCache.clear(),this.watchDetails.clear(),this.pollBaseMs.clear(),this.degradedHandlers.clear(),this.monitorRuntime.clear()}}export{D as AuctionManagementModule};
@@ -4,6 +4,8 @@ import { ReadableAtom } from 'nanostores';
4
4
  import { AuctionDisplayState } from '../experiences/distribution-monitor.types';
5
5
  import { DistributionSummary } from '../experiences/journey.types';
6
6
  import { CheckoutNextAction, GrantPaymentAffordance } from '../payment/types';
7
+ import { SelectionChoice, SelectionOptions } from '../selections/types';
8
+ import { AuctionBidInput } from './types';
7
9
  /**
8
10
  * Auction participation facts for one consumer.
9
11
  *
@@ -14,6 +16,8 @@ import { CheckoutNextAction, GrantPaymentAffordance } from '../payment/types';
14
16
  export interface AuctionSequenceConsumerState {
15
17
  /** Server auction status for this consumer. */
16
18
  status: AuctionConsumerStatus;
19
+ /** Recorded product and variant choice. */
20
+ selection?: SelectionChoice;
17
21
  /** Auction participation id, when the server has created one. */
18
22
  id?: string;
19
23
  /** Distribution id this auction participation belongs to. */
@@ -121,6 +125,7 @@ export interface AuctionGranted {
121
125
  * remaining affordances follow from this and nothing else.
122
126
  */
123
127
  settlement: GrantedSettlement;
128
+ /** Current product and variant selection for this grant. */
124
129
  selection: GrantedSelection;
125
130
  pendingAction?: CheckoutNextAction;
126
131
  }
@@ -148,7 +153,7 @@ export interface AuctionEnded {
148
153
  * on the phases where they are legal, and each action may reject if the server
149
154
  * has moved the sequence before the request is handled.
150
155
  */
151
- export type AuctionSequenceView = AuctionScheduledView | AuctionEnterableView | AuctionParticipatingView | AuctionSettlingView | AuctionGrantedView | AuctionEndedView;
156
+ export type AuctionSequenceView = AuctionScheduledView | AuctionEnterableView | AuctionParticipatingView | AuctionSettlingView | AuctionGrantedView | AuctionGrantedAwaitingAssignmentView | AuctionGrantedSelectionRequiredView | AuctionEndedView;
152
157
  /**
153
158
  * Scheduled auction view rendered before bidding opens.
154
159
  */
@@ -178,14 +183,21 @@ export interface AuctionEnterableView {
178
183
  };
179
184
  /** Consumer auction facts before the first bid. */
180
185
  consumer: AuctionSequenceConsumerState;
186
+ /** Live auction display facts: the price model, currency, and close time. */
187
+ display$: ReadableAtom<AuctionDisplayState>;
188
+ /** Load the current selection options. */
189
+ loadSelectionOptions(): Promise<SelectionOptions>;
181
190
  /**
182
191
  * Place the first auction bid.
183
192
  *
184
- * Legal only while this view is current. `amount` is a decimal money string.
185
- * The promise rejects if bidding is closed, the amount is invalid, or the
186
- * server rejects the bid.
193
+ * Legal only while this view is current. An English auction requires `input.amount`, a decimal
194
+ * money string; a Dutch auction clinches at the price the server is showing and refuses an
195
+ * amount. The promise rejects if the input does not match the mechanism, if bidding is closed,
196
+ * or if the server rejects the bid.
197
+ * `input.selection` names the product this bid is for on a selection-enabled auction; the server
198
+ * rejects a missing required choice with SELECTION_REQUIRED.
187
199
  */
188
- bid(amount: string): Promise<void>;
200
+ bid(input: AuctionBidInput): Promise<void>;
189
201
  }
190
202
  /**
191
203
  * Participating auction view rendered while the consumer is bidding.
@@ -198,16 +210,21 @@ export interface AuctionParticipatingView {
198
210
  mechanism: "auction";
199
211
  /** Consumer auction participation facts. */
200
212
  consumer: AuctionSequenceConsumerState;
201
- /** Live auction display facts, such as highest bid, bid count, and close time. */
213
+ /** Live auction display facts: the price model, currency, and close time. */
202
214
  display$: ReadableAtom<AuctionDisplayState>;
215
+ /** Load the current selection options. */
216
+ loadSelectionOptions(): Promise<SelectionOptions>;
203
217
  /**
204
218
  * Place or raise an auction bid.
205
219
  *
206
- * Legal only while this view is current. `amount` is a decimal money string.
207
- * The promise rejects if the amount is below the minimum increment, bidding is
208
- * closed, or the server rejects the bid.
220
+ * Legal only while this view is current. An English auction requires `input.amount`, a decimal
221
+ * money string at or above the price model's `minNextBid`; a Dutch auction clinches at the price
222
+ * the server is showing and refuses an amount. The promise rejects if the input does not match
223
+ * the mechanism, if bidding is closed, or if the server rejects the bid.
224
+ * `input.selection` names the product this bid is for on a selection-enabled auction; the server
225
+ * rejects a missing required choice with SELECTION_REQUIRED.
209
226
  */
210
- bid(amount: string): Promise<void>;
227
+ bid(input: AuctionBidInput): Promise<void>;
211
228
  }
212
229
  /**
213
230
  * Settling auction view rendered while the server computes the auction result.
@@ -240,8 +257,10 @@ interface AuctionGrantedViewBase {
240
257
  consumer: AuctionSequenceConsumerState;
241
258
  /** Admission credential issued by the server for this auction win. */
242
259
  grant: AdmissionGrant;
243
- /** Product choice carried by the admission register. */
244
- selection: GrantedSelection;
260
+ /** Resolved product and variant choice. */
261
+ selection: Extract<GrantedSelection, {
262
+ status: "resolved";
263
+ }>;
245
264
  }
246
265
  /** Granted auction view whose actions follow the server-projected settlement arm. */
247
266
  export type AuctionGrantedView = (AuctionGrantedViewBase & {
@@ -252,6 +271,10 @@ export type AuctionGrantedView = (AuctionGrantedViewBase & {
252
271
  }>;
253
272
  /** Claim the auction admission credential for checkout. */
254
273
  claim(): AdmissionGrant;
274
+ /** Load the current selection options. */
275
+ loadSelectionOptions(): Promise<SelectionOptions>;
276
+ /** Submit a product and variant choice. */
277
+ select(choice: SelectionChoice): Promise<void>;
255
278
  /** Embedded payment actions available before this tab claims the checkout. */
256
279
  payment: GrantPaymentAffordance;
257
280
  }) | (AuctionGrantedViewBase & {
@@ -262,6 +285,10 @@ export type AuctionGrantedView = (AuctionGrantedViewBase & {
262
285
  }>;
263
286
  /** Claim the auction admission credential; this tab has already claimed the checkout. */
264
287
  claim(): AdmissionGrant;
288
+ /** Load the current selection options. */
289
+ loadSelectionOptions(): Promise<SelectionOptions>;
290
+ /** Submit a product and variant choice. */
291
+ select(choice: SelectionChoice): Promise<void>;
265
292
  /** Absent after this tab claims the checkout. */
266
293
  payment?: undefined;
267
294
  }) | (AuctionGrantedViewBase & {
@@ -277,14 +304,10 @@ export type AuctionGrantedView = (AuctionGrantedViewBase & {
277
304
  * phase.
278
305
  */
279
306
  claim(): AdmissionGrant;
280
- }) | (AuctionGrantedViewBase & {
281
- /** Entry already carried the authorization, so no checkout remains after the win. */
282
- settlement: Extract<GrantedSettlement, {
283
- processor: "internal";
284
- mode: "pre_auth";
285
- }>;
286
- /** Absent because a pre-authorized win has no checkout to claim against. */
287
- claim?: undefined;
307
+ /** Load the current selection options. */
308
+ loadSelectionOptions(): Promise<SelectionOptions>;
309
+ /** Submit a product and variant choice. */
310
+ select(choice: SelectionChoice): Promise<void>;
288
311
  });
289
312
  /**
290
313
  * Ended auction view rendered once the auction journey is terminal.
@@ -299,4 +322,50 @@ export interface AuctionEndedView {
299
322
  /** Server-derived terminal outcome for the auction participation. */
300
323
  outcome: SequenceOutcome;
301
324
  }
325
+ /** Granted auction view waiting for a product assignment, with no available actions. */
326
+ export interface AuctionGrantedAwaitingAssignmentView {
327
+ /** Phase discriminator. */
328
+ phase: "granted";
329
+ /** Mechanism discriminator. */
330
+ mechanism: "auction";
331
+ /** Distribution associated with this grant. */
332
+ distribution: DistributionSummary & {
333
+ type: "auction";
334
+ };
335
+ /** Consumer participation associated with this grant. */
336
+ consumer: AuctionSequenceConsumerState;
337
+ /** Admission credential and its expiry. */
338
+ grant: AdmissionGrant;
339
+ /** How the grant settles. */
340
+ settlement: GrantedSettlement;
341
+ /** Assignment is not yet available. */
342
+ selection: Extract<GrantedSelection, {
343
+ status: "awaiting_assignment";
344
+ }>;
345
+ }
346
+ /** Granted auction view requiring a choice before checkout becomes available. */
347
+ export interface AuctionGrantedSelectionRequiredView {
348
+ /** Phase discriminator. */
349
+ phase: "granted";
350
+ /** Mechanism discriminator. */
351
+ mechanism: "auction";
352
+ /** Distribution associated with this grant. */
353
+ distribution: DistributionSummary & {
354
+ type: "auction";
355
+ };
356
+ /** Consumer participation associated with this grant. */
357
+ consumer: AuctionSequenceConsumerState;
358
+ /** Admission credential and its expiry. */
359
+ grant: AdmissionGrant;
360
+ /** How the grant settles. */
361
+ settlement: GrantedSettlement;
362
+ /** Product or variant choice still required. */
363
+ selection: Extract<GrantedSelection, {
364
+ status: "required";
365
+ }>;
366
+ /** Load the current selection options. */
367
+ loadSelectionOptions(): Promise<SelectionOptions>;
368
+ /** Submit a product and variant choice. */
369
+ select(choice: SelectionChoice): Promise<void>;
370
+ }
302
371
  export {};
@@ -1 +1 @@
1
- function t(t){return"bidding"===t.status||"winning"===t.status||"outbid"===t.status}export{t as isUnresolvedAuctionConsumer};
1
+ function t(t){return"winning"===t.status||"outbid"===t.status}export{t as isUnresolvedAuctionConsumer};
@@ -0,0 +1 @@
1
+ import{isMoneyString as r,subtractMoney as e,isMoneyLessThanOrEqualTo as t}from"../core/money.js";function o(o,i){const{nextDropAt:n,priceDropAmount:c,priceDropIntervalSeconds:s,currentPrice:u}=o;if(!n||!c||!s||s<=0)return o;if(!r(u)||!r(c))return o;const a=o.floorPrice??"0";if(!r(a))return o;const N=Date.parse(n);if(Number.isNaN(N))return o;const p=Date.parse(o.settleAt),f=Number.isNaN(p)?i:Math.min(i,p),m=1e3*s;let D=u,d=N;for(;void 0!==d&&f>=d&&(Number.isNaN(p)||d<p);){const r=e(D,c);if(t(r,a)){D=a,d=void 0;break}D=r,d+=m,!Number.isNaN(p)&&d>=p&&(d=void 0)}return D===u&&d===N?o:{...o,currentPrice:D,nextDropAt:void 0===d?void 0:new Date(d).toISOString()}}export{o as advanceDutchPricing};
@@ -2,4 +2,4 @@
2
2
  * Public auction surface. The runtime `AuctionManagementModule`/`AuctionModule`/`AuctionParticipation`
3
3
  * symbols from `./types` are internal and not exported here.
4
4
  */
5
- export type { AuctionConsumerStatus, AuctionDetails, AuctionStatus, AutoRebidConfig, Bid, BidResult } from './types';
5
+ export type { AuctionBidInput, AuctionConsumerStatus, AuctionDetails, AuctionLive, AuctionStatus, BidResult, } from './types';
@@ -1,5 +1,8 @@
1
+ import { AuctionLive } from '@fanfare-io/fanfare-sdk-contracts/auction';
1
2
  import { AuctionConsumerStatus } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
3
+ import { SelectionChoice } from '@fanfare-io/fanfare-sdk-contracts/selection';
2
4
  import { RequestOptions } from '../core/http';
5
+ export type { AuctionLive } from '@fanfare-io/fanfare-sdk-contracts/auction';
3
6
  export type { AuctionConsumerStatus } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
4
7
  /**
5
8
  * Auction participation stored in state
@@ -16,32 +19,42 @@ export interface AuctionParticipation {
16
19
  fingerprint?: string;
17
20
  }
18
21
  /**
19
- * Bid details
22
+ * What a consumer offers when they bid.
23
+ *
24
+ * The mechanism decides which half is legal: an English auction is bid at an amount the bidder
25
+ * names, and a Dutch auction clinches at the price the server is showing, so naming an amount
26
+ * there is refused rather than silently ignored. `selection` is the product the bid is for, on the
27
+ * auctions that let the consumer choose one.
20
28
  */
21
- export interface Bid {
22
- id?: string;
23
- amount: string;
24
- timestamp: string;
25
- isWinning: boolean;
26
- bidderAlias?: string;
27
- isYours?: boolean;
29
+ export interface AuctionBidInput {
30
+ /** Bid amount as a decimal money string. Required for English, refused for Dutch. */
31
+ amount?: string;
32
+ /** The product this bid is for, on a selection-enabled auction. */
33
+ selection?: SelectionChoice;
28
34
  }
29
35
  /**
30
36
  * Result of placing a bid
31
37
  */
32
38
  export interface BidResult {
33
39
  status: AuctionConsumerStatus;
40
+ /** Product binding disclosed only by a won response; its variant may remain unresolved. */
41
+ selection?: {
42
+ productId: string;
43
+ variantId?: string;
44
+ };
34
45
  amount: string;
35
46
  highestBid: string;
36
47
  bidCount: number;
37
48
  admissionGrant?: string;
38
49
  position?: number;
39
50
  /**
40
- * The bid committed but the server deferred a post-commit effect — a `won` with no
41
- * `admissionGrant` here is a live standing whose credential the status lane delivers on a
42
- * later read, not a settled outcome.
51
+ * The bid committed but the credential mint deferred — a `won` with no `admissionGrant` here is
52
+ * a live standing whose credential the status lane delivers on a later read, not a settled
53
+ * outcome.
43
54
  */
44
55
  degraded?: boolean;
56
+ /** Every display fact the auction surface needs, read after the commit. */
57
+ live: AuctionLive;
45
58
  }
46
59
  /**
47
60
  * Auction status from API
@@ -55,6 +68,25 @@ export interface AuctionStatus {
55
68
  bidCount?: number;
56
69
  position?: number;
57
70
  admissionGrant?: string;
71
+ /** WON: the grant's continuation deadline, as the admission register holds it. */
72
+ expiresAt?: string;
73
+ /**
74
+ * WON: the register could not be resolved on this read. It is never a terminal answer on its
75
+ * own — `/consumers/me` is the authority once the register has aged out.
76
+ */
77
+ degraded?: boolean;
78
+ /** WON: the terminal result the admission register derived for this win. */
79
+ outcome?: "completed" | "expired";
80
+ /** WON: the product the win is bound to. */
81
+ selection?: {
82
+ productId: string;
83
+ variantId?: string;
84
+ };
85
+ /**
86
+ * Every display fact the auction surface needs. Absent only where no wire response backed the
87
+ * read — a 404 on the status lane, or a poll that failed and fell back to cache.
88
+ */
89
+ live?: AuctionLive;
58
90
  }
59
91
  /**
60
92
  * Auction details from API
@@ -69,38 +101,33 @@ export interface AuctionDetails {
69
101
  settleAt: string;
70
102
  /** Organization-level currency code (ISO 4217). */
71
103
  currencyCode: string;
104
+ /** Which mechanism this auction runs. */
105
+ auctionType: "english" | "dutch";
72
106
  /** Reserve price (monetary string), if configured. */
73
107
  reservePrice?: string | null;
74
108
  /** Minimum bid increment (monetary string), if configured. */
75
109
  minBidIncrement?: string | null;
76
110
  /** Auto-extend duration (seconds), if configured. */
77
111
  autoExtendSeconds?: number | null;
112
+ /** Dutch: the price the descending ladder starts from (monetary string). */
113
+ startPrice?: string | null;
114
+ /** Dutch: the price the ladder never falls below (monetary string). */
115
+ floorPrice?: string | null;
116
+ /** Dutch: how much each drop subtracts (monetary string). */
117
+ priceDropAmount?: string | null;
118
+ /** Dutch: seconds between drops. */
119
+ priceDropIntervalSeconds?: number | null;
120
+ /** Dutch: units offered. */
121
+ quantity?: number | null;
78
122
  /** Timezone identifier (e.g. "America/New_York"). */
79
123
  timeZone: string;
80
124
  }
81
- /**
82
- * Auto-rebid configuration
83
- */
84
- export interface AutoRebidConfig {
85
- enabled: boolean;
86
- maxBid: string;
87
- increment: string;
88
- remainingBudget?: string;
89
- lastRebidAt?: string;
90
- rebidCount?: number;
91
- }
92
125
  /**
93
126
  * Auction module interface
94
127
  */
95
128
  export interface AuctionModule {
96
129
  get(auctionId: string): Promise<AuctionDetails>;
97
- bid(auctionId: string, amount: string, options?: RequestOptions): Promise<BidResult>;
98
- enter(auctionId: string, metadata?: Record<string, unknown>, options?: RequestOptions): Promise<void>;
99
- leave(auctionId: string, options?: RequestOptions): Promise<void>;
130
+ bid(auctionId: string, input: AuctionBidInput, options?: RequestOptions): Promise<BidResult>;
100
131
  status(auctionId: string): Promise<AuctionStatus>;
101
- getBidHistory(auctionId: string): Promise<Bid[]>;
102
- enableAutoRebid(auctionId: string, maxBid: string, increment: string): void;
103
- disableAutoRebid(auctionId: string): void;
104
- getAutoRebidConfig(auctionId: string): AutoRebidConfig | undefined;
105
132
  destroy(): void;
106
133
  }
@@ -131,7 +131,7 @@ export interface BeaconConfig {
131
131
  * Base URL for beacon API
132
132
  * When not specified, uses the environment-specific beacon URL
133
133
  * @example "http://localhost:4803" (development)
134
- * @example "https://beacon.fanfare.io" (production)
134
+ * @example "https://beacon.fanfare.io/api" (production)
135
135
  */
136
136
  baseUrl?: string;
137
137
  /**
@@ -10,7 +10,7 @@ export declare const API_URLS: {
10
10
  * Beacon API URLs by environment
11
11
  */
12
12
  export declare const BEACON_URLS: {
13
- readonly production: "https://beacon.fanfare.io";
13
+ readonly production: "https://beacon.fanfare.io/api";
14
14
  readonly development: "http://localhost:4803";
15
15
  };
16
16
  /**
@@ -1 +1 @@
1
- import{createError as i}from"../core/errors.js";const n={production:"https://consumer.fanfare.io/api",development:"http://localhost:4802"},e={production:"https://beacon.fanfare.io",development:"http://localhost:4803"},o=/* @__PURE__ */new Set(["https://consumer.fanfare.io"]),t=/* @__PURE__ */new Set(["https://beacon.fanfare.io"]),r={environment:"production",auth:{persistSession:!0,sessionDuration:3600},logging:{level:"error"}};class a{constructor(n){if(this.config={organizationId:n.organizationId,publishableKey:n.publishableKey,environment:n.environment??r.environment??"production",apiUrl:n.apiUrl,credentials:n.credentials??"include",debug:n.debug??!1,autoResume:n.autoResume??!0,auth:{...r.auth,...n.auth},logging:{...r.logging,...n.logging},sync:n.sync,beacon:n.beacon,features:{fingerprinting:n.features?.fingerprinting??!0}},!this.config.organizationId)throw i.invalidConfig("organizationId is required");if(!this.config.publishableKey)throw i.invalidConfig("publishableKey is required");"production"===this.config.environment&&(s("apiUrl",this.apiUrl,o),s("beacon.baseUrl",this.beaconUrl,t))}get apiUrl(){return this.config.apiUrl||n[this.config.environment]}get beaconUrl(){return this.config.beacon?.baseUrl||e[this.config.environment]}get(){return this.config}getValue(i){return this.config[i]}}function s(n,e,o){let t;try{t=new URL(e)}catch{throw i.invalidConfig(`${n} must be a valid URL in production`)}if("https:"!==t.protocol)throw i.invalidConfig(`${n} must use HTTPS in production`);if(!o.has(t.origin))throw i.invalidConfig(`${n} must use a trusted Fanfare destination in production`)}export{n as API_URLS,e as BEACON_URLS,a as Config,r as DEFAULT_CONFIG};
1
+ import{createError as i}from"../core/errors.js";const n={production:"https://consumer.fanfare.io/api",development:"http://localhost:4802"},e={production:"https://beacon.fanfare.io/api",development:"http://localhost:4803"},o=/* @__PURE__ */new Set(["https://consumer.fanfare.io"]),t=/* @__PURE__ */new Set(["https://beacon.fanfare.io"]),r={environment:"production",auth:{persistSession:!0,sessionDuration:3600},logging:{level:"error"}};class a{constructor(n){if(this.config={organizationId:n.organizationId,publishableKey:n.publishableKey,environment:n.environment??r.environment??"production",apiUrl:n.apiUrl,credentials:n.credentials??"include",debug:n.debug??!1,autoResume:n.autoResume??!0,auth:{...r.auth,...n.auth},logging:{...r.logging,...n.logging},sync:n.sync,beacon:n.beacon,features:{fingerprinting:n.features?.fingerprinting??!0}},!this.config.organizationId)throw i.invalidConfig("organizationId is required");if(!this.config.publishableKey)throw i.invalidConfig("publishableKey is required");"production"===this.config.environment&&(s("apiUrl",this.apiUrl,o),s("beacon.baseUrl",this.beaconUrl,t))}get apiUrl(){return this.config.apiUrl||n[this.config.environment]}get beaconUrl(){return this.config.beacon?.baseUrl||e[this.config.environment]}get(){return this.config}getValue(i){return this.config[i]}}function s(n,e,o){let t;try{t=new URL(e)}catch{throw i.invalidConfig(`${n} must be a valid URL in production`)}if("https:"!==t.protocol)throw i.invalidConfig(`${n} must use HTTPS in production`);if(!o.has(t.origin))throw i.invalidConfig(`${n} must use a trusted Fanfare destination in production`)}export{n as API_URLS,e as BEACON_URLS,a as Config,r as DEFAULT_CONFIG};