@fanfare-io/fanfare-sdk-core 0.2.0 → 0.4.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 (53) hide show
  1. package/dist/appointments/appointment.driver.d.ts +44 -0
  2. package/dist/appointments/appointment.driver.js +1 -0
  3. package/dist/appointments/appointment.sequence.d.ts +194 -0
  4. package/dist/auctions/auction.driver.d.ts +46 -0
  5. package/dist/auctions/auction.driver.js +1 -0
  6. package/dist/auctions/auction.module.js +1 -1
  7. package/dist/auctions/auction.sequence.d.ts +263 -0
  8. package/dist/core/client.js +1 -1
  9. package/dist/draws/draw.driver.d.ts +45 -0
  10. package/dist/draws/draw.driver.js +1 -0
  11. package/dist/draws/draw.module.js +1 -1
  12. package/dist/draws/draw.sequence.d.ts +249 -0
  13. package/dist/experiences/distribution-monitor.types.d.ts +92 -2
  14. package/dist/experiences/experience.module.js +1 -1
  15. package/dist/experiences/journey-view.d.ts +8 -10
  16. package/dist/experiences/journey-view.js +1 -1
  17. package/dist/experiences/journey.d.ts +45 -20
  18. package/dist/experiences/journey.js +1 -1
  19. package/dist/experiences/journey.machine.d.ts +3 -20
  20. package/dist/experiences/journey.machine.js +1 -1
  21. package/dist/experiences/journey.types.d.ts +422 -217
  22. package/dist/experiences/public.d.ts +1 -1
  23. package/dist/experiences/sequence-driver.d.ts +93 -0
  24. package/dist/experiences/types.d.ts +185 -23
  25. package/dist/internals.d.ts +1 -1
  26. package/dist/internals.js +1 -1
  27. package/dist/queues/queue.driver.d.ts +45 -0
  28. package/dist/queues/queue.driver.js +1 -0
  29. package/dist/queues/queue.module.js +1 -1
  30. package/dist/queues/queue.sequence.d.ts +222 -0
  31. package/dist/state/capability-token-registry.d.ts +1 -0
  32. package/dist/state/capability-token-registry.js +1 -1
  33. package/dist/state/store.d.ts +5 -1
  34. package/dist/state/store.js +1 -1
  35. package/dist/storefront.d.ts +14 -24
  36. package/dist/storefront.js +1 -1
  37. package/dist/test-utils/harness-scenarios.d.ts +8 -1
  38. package/dist/test-utils/harness-scenarios.js +1 -1
  39. package/dist/test-utils/mock-journey.d.ts +15 -4
  40. package/dist/test-utils/mock-journey.js +1 -1
  41. package/dist/test-utils/mock-sdk.js +1 -1
  42. package/dist/test-utils/verification-scenarios.d.ts +9 -1
  43. package/dist/test-utils/verification-scenarios.js +1 -1
  44. package/dist/timed-releases/timed-release.driver.d.ts +45 -0
  45. package/dist/timed-releases/timed-release.driver.js +1 -0
  46. package/dist/timed-releases/timed-release.module.d.ts +6 -1
  47. package/dist/timed-releases/timed-release.module.js +1 -1
  48. package/dist/timed-releases/timed-release.sequence.d.ts +227 -0
  49. package/dist/timed-releases/types.d.ts +16 -1
  50. package/dist/types/distribution-type.d.ts +1 -1
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +3 -3
@@ -1,8 +1,12 @@
1
+ import { SequenceOutcomeType } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
1
2
  import { HarnessScenarioKey } from './harness-scenarios';
2
- export type VerificationScenarioKey = "queue" | "admission-cycle" | "auth-queue" | "access-code-queue" | "draw" | "auth-draw" | "timed-release" | "auction";
3
+ export type VerificationScenarioKey = "queue" | "admission-cycle" | "appointment" | "auth-queue" | "access-code-queue" | "draw" | "auth-draw" | "timed-release" | "timed-release-completion" | "waitlist-lifecycle" | "auction";
3
4
  export type VerificationAction = {
4
5
  type: "clickButton";
5
6
  label: string;
7
+ } | {
8
+ type: "clickGridcell";
9
+ label: string;
6
10
  } | {
7
11
  type: "fillCredential";
8
12
  value: string;
@@ -21,6 +25,10 @@ export interface VerificationCheckpoint {
21
25
  visibleActions?: string[];
22
26
  hiddenActions?: string[];
23
27
  timerVisible?: boolean;
28
+ waitlistStatus?: "not_waitlisted" | "waitlisted" | "notified";
29
+ waitlistAbsent?: boolean;
30
+ /** Pin the ended sequence's outcome type from the bridge snapshot — ended-state copy is shared across most outcomes, so text checkpoints alone cannot distinguish them. */
31
+ outcomeType?: SequenceOutcomeType;
24
32
  }
25
33
  export interface VerificationStep {
26
34
  key: string;
@@ -1 +1 @@
1
- const e="sdk-harness@example.com",t="424242",i="WRONG1",n="VIP123",s=["queue","admission-cycle","auth-queue","access-code-queue","draw","auth-draw","timed-release","auction"],u=["queue","admission-cycle","auth-queue","access-code-queue","draw","timed-release","auction"],o={queue:{key:"queue",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]},steps:[{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in the Queue","Estimated wait"],hiddenTexts:["Continue to Shop"],visibleActions:["Leave Queue"]}}]},"admission-cycle":{key:"admission-cycle",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]},steps:[{key:"granted",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"granted",visibleTexts:["You're in!","Continue to Shop"],visibleActions:["Continue to Shop"]}}]},"auth-queue":{key:"auth-queue",initial:{journeyStage:"gated",visibleTexts:["Sign In","Enter your email or phone to continue"],visibleActions:["Continue"]},steps:[{key:"submitted-auth-credential",actions:[{type:"fillCredential",value:e},{type:"clickButton",label:"Continue"}],checkpoint:{journeyStage:"gated",visibleTexts:["Enter your code"],visibleActions:["Verify"]}},{key:"verified-auth",actions:[{type:"fillOtp",value:t}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]}},{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in the Queue","Estimated wait"],visibleActions:["Leave Queue"]}}]},"access-code-queue":{key:"access-code-queue",initial:{journeyStage:"gated",visibleTexts:["Enter Access Code","Enter your access code to continue"],visibleActions:["Submit"],hiddenActions:["Enter Queue"]},steps:[{key:"rejected-access-code",actions:[{type:"fillAccessCode",value:i},{type:"clickButton",label:"Submit"}],checkpoint:{journeyStage:"gated",hiddenActions:["Enter Queue"],visibleActions:["Submit"]}},{key:"unlocked-queue",actions:[{type:"fillAccessCode",value:n},{type:"clickButton",label:"Submit"}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]}},{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in the Queue","Estimated wait"],visibleActions:["Leave Queue"]}}]},draw:{key:"draw",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Draw"]},steps:[{key:"entered-draw",actions:[{type:"clickButton",label:"Enter Draw"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You've Entered","Good luck in the draw","Drawing in"],visibleActions:["Withdraw Entry"],timerVisible:!0}}]},"auth-draw":{key:"auth-draw",initial:{journeyStage:"gated",visibleTexts:["Sign In","Enter your email or phone to continue"],visibleActions:["Continue"]},steps:[{key:"submitted-auth-credential",actions:[{type:"fillCredential",value:e},{type:"clickButton",label:"Continue"}],checkpoint:{journeyStage:"gated",visibleTexts:["Enter your code"],visibleActions:["Verify"]}},{key:"verified-auth",actions:[{type:"fillOtp",value:t}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Draw"]}},{key:"entered-draw",actions:[{type:"clickButton",label:"Enter Draw"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You've Entered","Good luck in the draw","Drawing in"],visibleActions:["Withdraw Entry"],timerVisible:!0}}]},"timed-release":{key:"timed-release",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Flash Sale","Limited time access","Window closes in"],visibleActions:["Enter Sale"],timerVisible:!0},steps:[{key:"entered-sale",actions:[{type:"clickButton",label:"Enter Sale"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in!","Complete purchase in","Browse and complete your purchase before your session expires"],visibleActions:["Complete Purchase","Exit Sale"],timerVisible:!0}},{key:"completed-sale",actions:[{type:"clickButton",label:"Complete Purchase"}],checkpoint:{journeyStage:"routed",sequencePhase:"granted",visibleTexts:["You're in!","Continue to Shop"],visibleActions:["Continue to Shop"]}}]},auction:{key:"auction",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Auction","Bid for exclusive access","Auction ends in"],visibleActions:["Bid"],timerVisible:!0},steps:[{key:"bid-auction",actions:[{type:"clickButton",label:"Bid"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["Auction","Current bid","Auction ends in"],visibleActions:["Bid","Leave"],timerVisible:!0}},{key:"placed-bid",actions:[{type:"clickButton",label:"Bid"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're Winning!","Your winning bid","$110.00"],visibleActions:["Bid"]}}]}};function a(e){return e in o}function c(e){return o[e]}export{u as ADAPTER_SMOKE_SCENARIOS,i as DEFAULT_INVALID_ACCESS_CODE,n as DEFAULT_VALID_ACCESS_CODE,e as DEFAULT_VERIFICATION_EMAIL,t as DEFAULT_VERIFICATION_OTP,o as SDK_VERIFICATION_SCENARIOS,s as SHARED_HARNESS_VERIFICATION_SCENARIOS,c as getVerificationScenario,a as isVerificationScenarioKey};
1
+ const e="sdk-harness@example.com",t="424242",i="WRONG1",n="VIP123",s=["queue","admission-cycle","appointment","auth-queue","access-code-queue","draw","auth-draw","timed-release","timed-release-completion","auction"],o=["queue","admission-cycle","auth-queue","access-code-queue","draw","timed-release","auction"],a={queue:{key:"queue",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]},steps:[{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in the Queue","Estimated wait"],hiddenTexts:["Continue to Shop"],visibleActions:["Leave Queue"]}}]},"admission-cycle":{key:"admission-cycle",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]},steps:[{key:"granted",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"granted",visibleTexts:["You're in!","Continue to Shop"],visibleActions:["Continue to Shop"]}}]},appointment:{key:"appointment",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Book an Appointment"],visibleActions:["Select a time"]},steps:[{key:"selected-slot",actions:[{type:"clickGridcell",label:"9:00 AM"}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Book an Appointment"],visibleActions:["Continue · 9:00 AM"]}},{key:"booked-appointment",actions:[{type:"clickButton",label:"Continue · 9:00 AM"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["Appointment Booked","Suite 100","9:00 AM"],visibleActions:["Reschedule","Cancel"]}},{key:"started-reschedule",actions:[{type:"clickButton",label:"Reschedule"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["Reschedule Appointment"],visibleActions:["Select a time"]}},{key:"selected-reschedule-slot",actions:[{type:"clickGridcell",label:"10:30 AM"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["Reschedule Appointment"],visibleActions:["Continue · 10:30 AM"]}},{key:"rescheduled-appointment",actions:[{type:"clickButton",label:"Continue · 10:30 AM"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["Appointment Booked","Suite 100","10:30 AM"],visibleActions:["Reschedule","Cancel"]}}]},"auth-queue":{key:"auth-queue",initial:{journeyStage:"gated",visibleTexts:["Sign In","Enter your email or phone to continue"],visibleActions:["Continue"]},steps:[{key:"submitted-auth-credential",actions:[{type:"fillCredential",value:e},{type:"clickButton",label:"Continue"}],checkpoint:{journeyStage:"gated",visibleTexts:["Enter your code"],visibleActions:["Verify"]}},{key:"verified-auth",actions:[{type:"fillOtp",value:t}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]}},{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in the Queue","Estimated wait"],visibleActions:["Leave Queue"]}}]},"access-code-queue":{key:"access-code-queue",initial:{journeyStage:"gated",visibleTexts:["Enter Access Code","Enter your access code to continue"],visibleActions:["Submit"],hiddenActions:["Enter Queue"]},steps:[{key:"rejected-access-code",actions:[{type:"fillAccessCode",value:i},{type:"clickButton",label:"Submit"}],checkpoint:{journeyStage:"gated",hiddenActions:["Enter Queue"],visibleActions:["Submit"]}},{key:"unlocked-queue",actions:[{type:"fillAccessCode",value:n},{type:"clickButton",label:"Submit"}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Queue"]}},{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in the Queue","Estimated wait"],visibleActions:["Leave Queue"]}}]},draw:{key:"draw",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Draw"]},steps:[{key:"entered-draw",actions:[{type:"clickButton",label:"Enter Draw"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You've Entered","Good luck in the draw","Drawing in"],visibleActions:["Withdraw Entry"],timerVisible:!0}}]},"auth-draw":{key:"auth-draw",initial:{journeyStage:"gated",visibleTexts:["Sign In","Enter your email or phone to continue"],visibleActions:["Continue"]},steps:[{key:"submitted-auth-credential",actions:[{type:"fillCredential",value:e},{type:"clickButton",label:"Continue"}],checkpoint:{journeyStage:"gated",visibleTexts:["Enter your code"],visibleActions:["Verify"]}},{key:"verified-auth",actions:[{type:"fillOtp",value:t}],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",visibleActions:["Enter Draw"]}},{key:"entered-draw",actions:[{type:"clickButton",label:"Enter Draw"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You've Entered","Good luck in the draw","Drawing in"],visibleActions:["Withdraw Entry"],timerVisible:!0}}]},"timed-release":{key:"timed-release",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Flash Sale","Limited time access","Window closes in"],visibleActions:["Enter Sale"],timerVisible:!0},steps:[{key:"entered-sale",actions:[{type:"clickButton",label:"Enter Sale"}],checkpoint:{journeyStage:"routed",sequencePhase:"granted",visibleTexts:["You're in!","Continue to Shop"],visibleActions:["Continue to Shop"],timerVisible:!0}}]},"timed-release-completion":{key:"timed-release-completion",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Flash Sale","Limited time access","Window closes in"],visibleActions:["Enter Sale"],timerVisible:!0},steps:[{key:"entered-grantless-sale",actions:[{type:"clickButton",label:"Enter Sale"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're in!","Complete purchase in","Browse and complete your purchase"],visibleActions:["Complete Purchase"],hiddenActions:["Continue to Shop"],timerVisible:!0}},{key:"completed-sale",actions:[{type:"clickButton",label:"Complete Purchase"}],checkpoint:{journeyStage:"routed",sequencePhase:"ended",outcomeType:"completed",visibleTexts:["Purchase complete","Thank you for your purchase"],hiddenActions:["Complete Purchase","Continue to Shop"]}}]},"waitlist-lifecycle":{key:"waitlist-lifecycle",initial:{journeyStage:"routed",sequencePhase:"scheduled",waitlistStatus:"not_waitlisted",visibleActions:["Enter Waitlist"],hiddenActions:["Enter Queue"]},steps:[{key:"joined-waitlist",actions:[{type:"clickButton",label:"Enter Waitlist"}],checkpoint:{journeyStage:"routed",sequencePhase:"scheduled",waitlistStatus:"waitlisted",visibleTexts:["You're on the Waitlist!"],visibleActions:["Leave Waitlist"],hiddenActions:["Enter Queue"]}},{key:"notified-waitlist",actions:[],checkpoint:{journeyStage:"routed",sequencePhase:"scheduled",waitlistStatus:"notified",visibleActions:["Leave Waitlist"],hiddenActions:["Enter Queue"]}},{key:"opened-queue",actions:[],checkpoint:{journeyStage:"routed",sequencePhase:"enterable",waitlistAbsent:!0,visibleActions:["Enter Queue"],hiddenActions:["Enter Waitlist","Leave Waitlist"]}},{key:"entered-queue",actions:[{type:"clickButton",label:"Enter Queue"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",waitlistAbsent:!0,visibleTexts:["You're in the Queue","Estimated wait"],visibleActions:["Leave Queue"]}}]},auction:{key:"auction",initial:{journeyStage:"routed",sequencePhase:"enterable",visibleTexts:["Auction","Bid for exclusive access","Auction ends in"],visibleActions:["Bid"],timerVisible:!0},steps:[{key:"bid-auction",actions:[{type:"clickButton",label:"Bid"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["Auction","Current bid","Auction ends in"],visibleActions:["Bid","Leave"],timerVisible:!0}},{key:"placed-bid",actions:[{type:"clickButton",label:"Bid"}],checkpoint:{journeyStage:"routed",sequencePhase:"participating",visibleTexts:["You're Winning!","Your winning bid","$110.00"],visibleActions:["Bid"]}}]}};function c(e){return e in a}function u(e){return a[e]}export{o as ADAPTER_SMOKE_SCENARIOS,i as DEFAULT_INVALID_ACCESS_CODE,n as DEFAULT_VALID_ACCESS_CODE,e as DEFAULT_VERIFICATION_EMAIL,t as DEFAULT_VERIFICATION_OTP,a as SDK_VERIFICATION_SCENARIOS,s as SHARED_HARNESS_VERIFICATION_SCENARIOS,u as getVerificationScenario,c as isVerificationScenarioKey};
@@ -0,0 +1,45 @@
1
+ import { DistributionMonitor, MonitorUpdate } from '../experiences/distribution-monitor.types';
2
+ import { DistributionSummary, JourneyEvent, JourneyEventAudience, JourneyEventKind, JourneyEventSeverity, MechanismConsumerState, Participation, SequenceOutcome, SequenceState } from '../experiences/journey.types';
3
+ import { SequenceDriver, SequenceProjectionFacts } from '../experiences/sequence-driver';
4
+ import { TimedReleaseModule } from './types';
5
+ type TimedReleaseMonitorModule = TimedReleaseModule & Pick<DistributionMonitor, "startMonitoring" | "stopMonitoring">;
6
+ /**
7
+ * Narrow shell and module capabilities required by the timed-release driver.
8
+ */
9
+ export interface TimedReleaseDriverDependencies {
10
+ /** Timed-release module functions used by timed-release actions and monitoring. */
11
+ timedReleases: Pick<TimedReleaseMonitorModule, "enter" | "leave" | "complete" | "startMonitoring" | "stopMonitoring">;
12
+ /** Serialize a timed-release action with the shell's operation queue. */
13
+ runSerializedOperation(operation: () => Promise<void>): Promise<void>;
14
+ /** Read the current routed sequence before validating a timed-release action. */
15
+ getCurrentSequence(): SequenceState | undefined;
16
+ /** Commit a concrete next timed-release sequence into the shell. */
17
+ commit(next: SequenceState, opts?: {
18
+ events?: JourneyEvent[];
19
+ }): void;
20
+ /** Apply shell-owned grant and terminal lifecycle side effects. */
21
+ applyMonitorUpdate(update: MonitorUpdate): void;
22
+ /** Ensure the shell-owned display atom exists for this timed-release participation. */
23
+ ensureDisplayAtom(participation: Participation): void;
24
+ /** Stop shell runtime monitoring and optionally clear display state. */
25
+ stopRuntimeMonitoring(clearDisplay?: boolean): void;
26
+ /** Append a shell-visible journey event to the snapshot. */
27
+ emitEvent(kind: JourneyEventKind, severity: JourneyEventSeverity, audience: JourneyEventAudience, message: string, detail?: Record<string, unknown>): void;
28
+ }
29
+ /**
30
+ * Create the timed-release sequence driver for one journey instance.
31
+ */
32
+ export declare function createTimedReleaseDriver(deps: TimedReleaseDriverDependencies): SequenceDriver;
33
+ /**
34
+ * Project timed-release server facts into a timed-release sequence state.
35
+ */
36
+ export declare function projectTimedReleaseSequence(facts: SequenceProjectionFacts): SequenceState;
37
+ /**
38
+ * Return the timed-release consumer state that represents an issued grant.
39
+ */
40
+ export declare function withTimedReleaseGrantedConsumer(consumer: MechanismConsumerState | undefined, distribution: DistributionSummary): MechanismConsumerState;
41
+ /**
42
+ * Return the timed-release consumer state that represents a terminal outcome.
43
+ */
44
+ export declare function withTimedReleaseTerminalConsumer(consumer: MechanismConsumerState | undefined, outcome?: SequenceOutcome): MechanismConsumerState | undefined;
45
+ export {};
@@ -0,0 +1 @@
1
+ import{createError as e}from"../core/errors.js";function t(e){return new r(e)}function i(t){const{distribution:i,consumer:s,waitlist:n,grant:r,outcome:o}=t;if(!i)return{phase:"unavailable"};const c=function(t){if("timed_release"!==t.type)throw e.validationError("Timed-release driver received a non-timed-release distribution");return t}(i),u=s&&"timed_release"===s.mechanism?function(e){const{mechanism:t,...i}=e;return i}(s):a(i);if(r)return"completed"===o?.type?d(c,u,o):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===c.lifecycle?{phase:"enterable",mechanism:"timed_release",distribution:c,consumer:a(c)}:d(c,u,{type:"expired"}):{phase:"granted",mechanism:"timed_release",distribution:c,consumer:u,grant:r};const m=o??function(e){return"completed"===e.status?{type:"completed"}:"denied"===e.status?{type:"denied",reason:e.deniedReason}:"left"===e.status?{type:"left"}:void 0}(u);if(m)return d(c,u,m);if(function(e){return"entered"===e.status}(u))return{phase:"participating",mechanism:"timed_release",distribution:c,consumer:u};if("scheduled"===c.lifecycle){const e=n&&n.waitlistId===c.waitlistId?n:c.waitlistId?{waitlistId:c.waitlistId,status:"not_waitlisted"}:void 0;return{phase:"scheduled",mechanism:"timed_release",distribution:c,...e?{waitlist:e}:{}}}return"open"===c.lifecycle?{phase:"enterable",mechanism:"timed_release",distribution:c,consumer:u}:d(c,u,{type:"closed"})}function s(e,t){return"timed_release"===e?.mechanism?{...e,status:"completed"}:{mechanism:"timed_release",status:"completed",id:t.id,distributionId:t.id}}function n(e,t){return e&&"timed_release"===e.mechanism&&t?"left"===t.type?{...e,status:"left"}:"denied"===t.type?{...e,status:"denied",deniedReason:t.reason}:"completed"===t.type?{...e,status:"completed"}:{...e,status:"not_entered"}:e}class r{constructor(e){this.deps=e,this.mechanism="timed_release"}project(e){return i(e)}buildView(t,i){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const s=function(t){if(!("mechanism"in t)||"timed_release"!==t.mechanism)throw e.validationError("Timed-release driver received a non-timed-release sequence");return t}(t);switch(s.phase){case"scheduled":return{phase:"scheduled",mechanism:"timed_release",distribution:s.distribution,startsAt:s.distribution.startsAt??s.distribution.opensAt,waitlist:s.waitlist?{status:s.waitlist.status,join:()=>i.joinWaitlist(),leave:()=>i.exitWaitlist()}:void 0};case"enterable":return{phase:"enterable",mechanism:"timed_release",distribution:s.distribution,consumer:s.consumer,enter:()=>this.enter()};case"participating":return{phase:"participating",mechanism:"timed_release",consumer:s.consumer,display$:i.displayAtom,leave:()=>this.leave(),complete:()=>this.complete()};case"granted":return{phase:"granted",mechanism:"timed_release",distribution:s.distribution,consumer:s.consumer,grant:s.grant,claim:()=>i.claim()};case"ended":return{phase:"ended",mechanism:"timed_release",consumer:s.consumer,outcome:s.outcome}}}startMonitoring(e){this.activeMonitorId=e.participation.id,this.deps.timedReleases.startMonitoring(e.participation.id,{displayAtom:e.displayAtom},e.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.timedReleases.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation()})}async enterWithinOperation(){const e=this.currentTimedReleaseSequence("enterable","No open timed release to enter").distribution,t=function(e){if("ENTERED"===e.status)return e.admissionGrant?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null;if("COMPLETED"===e.status)return{type:"ended",outcome:{type:"completed"}};return{type:"ended",outcome:{type:"LEFT"===e.status?"left":"denied",reason:"reason"in e?e.reason:void 0}}}(await this.deps.timedReleases.enter(e.id,void 0));if(t)return void this.deps.applyMonitorUpdate(t);const s={mechanism:"timed_release",status:"entered",id:n=e.id,distributionId:n};var n;this.deps.ensureDisplayAtom({id:e.id,type:"timed_release"}),this.deps.commit(i({distribution:e,consumer:s})),this.deps.emitEvent("sequence_change","success","user","Entered timed release",{distributionId:e.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const e=this.currentTimedReleaseSequence("participating"),t=e.consumer.distributionId??e.consumer.id??e.distribution.id;this.deps.stopRuntimeMonitoring(!0),await this.deps.timedReleases.leave(t),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"timed_release"})}async complete(){await this.deps.runSerializedOperation(async()=>{const e=this.currentTimedReleaseSequence("participating"),t=e.consumer.distributionId??e.consumer.id??e.distribution.id,i=this.deps.timedReleases.complete(t);i?this.deps.applyMonitorUpdate({type:"granted",token:i}):this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"completed"}}),this.deps.emitEvent("sequence_change","success","user","Completed timed release",{timedReleaseId:t})})}currentTimedReleaseSequence(t,i="Not participating in a timed release"){const s=this.deps.getCurrentSequence();if(!s||s.phase!==t||!("mechanism"in s)||"timed_release"!==s.mechanism)throw e.validationError(i);return s}}function a(e){return{status:"not_entered",distributionId:e.id}}function d(e,t,i){return{phase:"ended",mechanism:"timed_release",distribution:e,consumer:t,outcome:{...i,distributionType:i.distributionType??e.type,distributionId:i.distributionId??e.id,at:i.at??/* @__PURE__ */(new Date).toISOString()}}}export{t as createTimedReleaseDriver,i as projectTimedReleaseSequence,s as withTimedReleaseGrantedConsumer,n as withTimedReleaseTerminalConsumer};
@@ -16,7 +16,12 @@ export declare class TimedReleaseManagementModule implements TimedReleaseModule
16
16
  get(timedReleaseId: string): Promise<TimedRelease>;
17
17
  enter(timedReleaseId: string, variantId?: string, options?: RequestOptions): Promise<TimedReleaseConsumerState>;
18
18
  leave(timedReleaseId: string, options?: RequestOptions): Promise<void>;
19
- complete(timedReleaseId: string, options?: RequestOptions): Promise<string | undefined>;
19
+ /**
20
+ * Admit = enter: the admission credential is minted and returned at entry, so
21
+ * finishing makes no network call — it resolves the credential held from entry
22
+ * (and refreshed by status re-mints) for the checkout claim.
23
+ */
24
+ complete(timedReleaseId: string): string | undefined;
20
25
  status(timedReleaseId: string): Promise<TimedReleaseConsumerState>;
21
26
  startMonitoring(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void): void;
22
27
  stopMonitoring(id: string): void;
@@ -1 +1 @@
1
- import{createError as t}from"../core/errors.js";import{getLogger as e}from"../core/logger.js";import{DistributionMonitorRuntime as i}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as s}from"../state/events.js";import{getSDKStore as r}from"../state/store.js";class n{constructor(t){this.logger=e(),this.events=s(),this.monitorRuntime=new i,this.trackedParticipations=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return r()}async get(t){try{return await this.http.get(`/timed-releases/${t}`)}catch(e){throw this.logger.error("Failed to get timed release",{timedReleaseId:t,error:e}),e}}async enter(e,i,s){const r=this.lifecycleGeneration,n=this.monitorGenerations.get(e);if(!this.store.session)throw t.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(e);try{this.logger.info("Entering timed release",{timedReleaseId:e});const t=await this.http.post(`/timed-releases/${e}/enter`,{variantId:i},s),a=this.buildTransition(e,t,{source:"enter"});return this.canApplyAsyncResult(e,r,n,o)?(this.applyTransition(e,a),t):t}catch(a){if(!this.canApplyAsyncResult(e,r,n,o))throw a;throw this.logger.error("Failed to enter timed release",{timedReleaseId:e,error:a}),this.events.emit("timed_release:error",{timedReleaseId:e,error:a}),a}}async leave(t,e){const i=this.lifecycleGeneration,s=this.monitorGenerations.get(t),r=this.bumpOperationGeneration(t);try{this.logger.info("Leaving timed release",{timedReleaseId:t}),await this.http.post(`/timed-releases/${t}/leave`,{},e);const n=this.buildTransition(t,{status:"LEFT",leftAt:/* @__PURE__ */(new Date).toISOString()},{source:"leave"});if(!this.canApplyAsyncResult(t,i,s,r))return;this.applyTransition(t,n)}catch(n){if(!this.canApplyAsyncResult(t,i,s,r))throw n;throw this.logger.error("Failed to leave timed release",{timedReleaseId:t,error:n}),this.events.emit("timed_release:error",{timedReleaseId:t,error:n}),n}}async complete(t,e){const i=this.lifecycleGeneration,s=this.monitorGenerations.get(t),r=this.bumpOperationGeneration(t);try{this.logger.info("Completing timed release",{timedReleaseId:t});const n=(await this.http.post(`/timed-releases/${t}/complete`,{},e)).admissionGrant,o=this.buildTransition(t,{status:"COMPLETED",completedAt:/* @__PURE__ */(new Date).toISOString()},{source:"complete",grant:n});return this.canApplyAsyncResult(t,i,s,r)?(this.applyTransition(t,o),n):n}catch(n){if(!this.canApplyAsyncResult(t,i,s,r))throw n;throw this.logger.error("Failed to complete timed release",{timedReleaseId:t,error:n}),this.events.emit("timed_release:error",{timedReleaseId:t,error:n}),n}}async status(t){const e=this.lifecycleGeneration,i=this.monitorGenerations.get(t),s=this.bumpOperationGeneration(t);try{const r=await this.http.get(`/timed-releases/${t}/status`),n=this.buildTransition(t,r,{source:"status"});return this.canApplyAsyncResult(t,e,i,s)?(this.applyTransition(t,n),this.events.emit("timed_release:status-updated",{timedReleaseId:t,status:r.status}),r):r}catch(r){if(r&&"object"==typeof r&&"status"in r&&404===r.status){const r={status:"NOT_ENTERED"},n=this.buildTransition(t,r,{source:"status"});return this.canApplyAsyncResult(t,e,i,s)?(this.applyTransition(t,n),this.events.emit("timed_release:status-updated",{timedReleaseId:t,status:r.status}),r):r}if(!this.canApplyAsyncResult(t,e,i,s))return{status:"NOT_ENTERED"};throw this.logger.error("Failed to get timed release status",{timedReleaseId:t,error:r}),this.events.emit("timed_release:error",{timedReleaseId:t,error:r}),r}}startMonitoring(t,e,i){this.bumpMonitorGeneration(t),this.monitorRuntime.start(t,e,i);const s=this.monitorRuntime.getDisplayAtom(t);s&&s.set({type:"timed_release",enteredAt:this.trackedParticipations.get(t)?.enteredAt})}stopMonitoring(t){this.bumpMonitorGeneration(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)}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))}buildTransition(t,e,{source:i,grant:s}){switch(e.status){case"ENTERED":return{trackedParticipation:{timedReleaseId:t,enteredAt:e.enteredAt,selectedVariantId:e.selectedVariantId},events:"enter"===i?[{type:"timed_release:entered",payload:{timedReleaseId:t,enteredAt:e.enteredAt}}]:[],monitorUpdate:null,stopMonitoring:!1};case"COMPLETED":return{trackedParticipation:null,events:[{type:"timed_release:completed",payload:{timedReleaseId:t,completedAt:e.completedAt}}],monitorUpdate:"complete"===i&&s?{type:"granted",token:s}:null,stopMonitoring:"complete"===i||"status"===i};case"LEFT":return{trackedParticipation:null,events:[{type:"timed_release:left",payload:{timedReleaseId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"left"}}:null,stopMonitoring:"leave"===i||"status"===i};case"DENIED":return{trackedParticipation:null,events:[],monitorUpdate:"enter"===i||"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"enter"===i||"status"===i};case"NOT_ENTERED":return{trackedParticipation:null,events:[],monitorUpdate:"enter"===i||"status"===i?{type:"ended",outcome:{type:"closed",reason:"not_entered"}}:null,stopMonitoring:"enter"===i||"status"===i}}}applyTransition(t,e){e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t);const i=this.monitorRuntime.getDisplayAtom(t);i&&(e.trackedParticipation?i.set({type:"timed_release",enteredAt:e.trackedParticipation.enteredAt}):i.set({type:"timed_release"}));for(const s of e.events)this.events.emit(s.type,s.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring&&this.stopMonitoring(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.monitorRuntime.clear()}}export{n as TimedReleaseManagementModule};
1
+ import{createError as t}from"../core/errors.js";import{getLogger as e}from"../core/logger.js";import{DistributionMonitorRuntime as i}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as s}from"../state/events.js";import{getSDKStore as r}from"../state/store.js";class n{constructor(t){this.logger=e(),this.events=s(),this.monitorRuntime=new i,this.trackedParticipations=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return r()}async get(t){try{return await this.http.get(`/timed-releases/${t}`)}catch(e){throw this.logger.error("Failed to get timed release",{timedReleaseId:t,error:e}),e}}async enter(e,i,s){const r=this.lifecycleGeneration,n=this.monitorGenerations.get(e);if(!this.store.session)throw t.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(e);try{this.logger.info("Entering timed release",{timedReleaseId:e});const t=await this.http.post(`/timed-releases/${e}/enter`,{variantId:i},s),a=this.buildTransition(e,t,{source:"enter"});return this.canApplyAsyncResult(e,r,n,o)?(this.applyTransition(e,a),t):t}catch(a){if(!this.canApplyAsyncResult(e,r,n,o))throw a;throw this.logger.error("Failed to enter timed release",{timedReleaseId:e,error:a}),this.events.emit("timed_release:error",{timedReleaseId:e,error:a}),a}}async leave(t,e){const i=this.lifecycleGeneration,s=this.monitorGenerations.get(t),r=this.bumpOperationGeneration(t);try{this.logger.info("Leaving timed release",{timedReleaseId:t}),await this.http.post(`/timed-releases/${t}/leave`,{},e);const n=this.buildTransition(t,{status:"LEFT",leftAt:/* @__PURE__ */(new Date).toISOString()},{source:"leave"});if(!this.canApplyAsyncResult(t,i,s,r))return;this.applyTransition(t,n)}catch(n){if(!this.canApplyAsyncResult(t,i,s,r))throw n;throw this.logger.error("Failed to leave timed release",{timedReleaseId:t,error:n}),this.events.emit("timed_release:error",{timedReleaseId:t,error:n}),n}}complete(t){return this.trackedParticipations.get(t)?.admissionGrant}async status(t){const e=this.lifecycleGeneration,i=this.monitorGenerations.get(t),s=this.bumpOperationGeneration(t);try{const r=await this.http.get(`/timed-releases/${t}/status`),n=this.buildTransition(t,r,{source:"status"});return this.canApplyAsyncResult(t,e,i,s)?(this.applyTransition(t,n),this.events.emit("timed_release:status-updated",{timedReleaseId:t,status:r.status}),r):r}catch(r){if(r&&"object"==typeof r&&"status"in r&&404===r.status){const r={status:"NOT_ENTERED"},n=this.buildTransition(t,r,{source:"status"});return this.canApplyAsyncResult(t,e,i,s)?(this.applyTransition(t,n),this.events.emit("timed_release:status-updated",{timedReleaseId:t,status:r.status}),r):r}if(!this.canApplyAsyncResult(t,e,i,s))return{status:"NOT_ENTERED"};throw this.logger.error("Failed to get timed release status",{timedReleaseId:t,error:r}),this.events.emit("timed_release:error",{timedReleaseId:t,error:r}),r}}startMonitoring(t,e,i){this.bumpMonitorGeneration(t),this.monitorRuntime.start(t,e,i);const s=this.monitorRuntime.getDisplayAtom(t);s&&s.set({type:"timed_release",enteredAt:this.trackedParticipations.get(t)?.enteredAt})}stopMonitoring(t){this.bumpMonitorGeneration(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)}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))}buildTransition(t,e,{source:i}){switch(e.status){case"ENTERED":return{trackedParticipation:{timedReleaseId:t,enteredAt:e.enteredAt,selectedVariantId:e.selectedVariantId,admissionGrant:e.admissionGrant,expiresAt:e.expiresAt},events:"enter"===i?[{type:"timed_release:entered",payload:{timedReleaseId:t,enteredAt:e.enteredAt}}]:[],monitorUpdate:e.admissionGrant?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:!1};case"COMPLETED":return{trackedParticipation:null,events:[{type:"timed_release:completed",payload:{timedReleaseId:t,completedAt:e.completedAt}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"completed"}}:null,stopMonitoring:"status"===i};case"LEFT":return{trackedParticipation:null,events:[{type:"timed_release:left",payload:{timedReleaseId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"left"}}:null,stopMonitoring:"leave"===i||"status"===i};case"DENIED":return{trackedParticipation:null,events:[],monitorUpdate:"enter"===i||"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"enter"===i||"status"===i};case"NOT_ENTERED":return{trackedParticipation:null,events:[],monitorUpdate:"enter"===i||"status"===i?{type:"ended",outcome:{type:"closed",reason:"not_entered"}}:null,stopMonitoring:"enter"===i||"status"===i}}}applyTransition(t,e){e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t);const i=this.monitorRuntime.getDisplayAtom(t);i&&(e.trackedParticipation?i.set({type:"timed_release",enteredAt:e.trackedParticipation.enteredAt}):i.set({type:"timed_release"}));for(const s of e.events)this.events.emit(s.type,s.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring&&this.stopMonitoring(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.monitorRuntime.clear()}}export{n as TimedReleaseManagementModule};
@@ -0,0 +1,227 @@
1
+ import { AdmissionGrant, SequenceOutcome, TimedReleaseConsumerStatus } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
2
+ import { ReadableAtom } from 'nanostores';
3
+ import { TimedReleaseDisplayState } from '../experiences/distribution-monitor.types';
4
+ import { DistributionSummary, WaitlistAttachment, WaitlistAttachmentView } from '../experiences/journey.types';
5
+ /**
6
+ * Timed release participation facts for one consumer.
7
+ *
8
+ * The server is authoritative for every field. Timestamps are ISO 8601 strings.
9
+ * Optional fields are present only after the matching timed release lifecycle
10
+ * event has occurred.
11
+ */
12
+ export interface TimedReleaseSequenceConsumerState {
13
+ /** Server timed release status for this consumer. */
14
+ status: TimedReleaseConsumerStatus;
15
+ /** Timed release participation id, when the server has created one. */
16
+ id?: string;
17
+ /** Distribution id this timed release participation belongs to. */
18
+ distributionId?: string;
19
+ /** ISO 8601 timestamp for when the consumer entered the timed release. */
20
+ enteredAt?: string;
21
+ /** Opaque admission credential token, when one has been issued inline. */
22
+ admissionGrant?: string;
23
+ /** ISO 8601 timestamp for when the consumer was granted admission. */
24
+ grantedAt?: string;
25
+ /** Server-provided denial reason for denied timed release outcomes. */
26
+ deniedReason?: string;
27
+ }
28
+ /**
29
+ * A timed release sequence as seen by one consumer.
30
+ *
31
+ * Lifecycle: scheduled -> enterable -> participating -> granted -> ended. Entry
32
+ * reserves the timed release position, the participating display tracks the
33
+ * live release window, and completion may surface the held admission grant.
34
+ */
35
+ export type TimedReleaseSequence = TimedReleaseScheduled | TimedReleaseEnterable | TimedReleaseParticipating | TimedReleaseGranted | TimedReleaseEnded;
36
+ /**
37
+ * The timed release exists but entry has not opened yet.
38
+ */
39
+ export interface TimedReleaseScheduled {
40
+ /** Mechanism discriminator for timed release narrowing. */
41
+ mechanism: "timed_release";
42
+ /** Phase discriminator; scheduled timed releases cannot be entered yet. */
43
+ phase: "scheduled";
44
+ /** Timed release distribution facts from the journey projector. */
45
+ distribution: DistributionSummary & {
46
+ type: "timed_release";
47
+ };
48
+ /** Scheduled-only waitlist attachment, cleared when the timed release opens. */
49
+ waitlist?: WaitlistAttachment;
50
+ }
51
+ /**
52
+ * The timed release is open and this consumer has not entered it.
53
+ */
54
+ export interface TimedReleaseEnterable {
55
+ /** Mechanism discriminator for timed release narrowing. */
56
+ mechanism: "timed_release";
57
+ /** Phase discriminator; `enter()` is legal on the matching view. */
58
+ phase: "enterable";
59
+ /** Open timed release distribution facts from the journey projector. */
60
+ distribution: DistributionSummary & {
61
+ type: "timed_release";
62
+ };
63
+ /** Consumer timed release facts before entry. */
64
+ consumer: TimedReleaseSequenceConsumerState;
65
+ }
66
+ /**
67
+ * The consumer has entered the timed release.
68
+ */
69
+ export interface TimedReleaseParticipating {
70
+ /** Mechanism discriminator for timed release narrowing. */
71
+ mechanism: "timed_release";
72
+ /** Phase discriminator; timed release display is live while participating. */
73
+ phase: "participating";
74
+ /** Timed release distribution facts from the journey projector. */
75
+ distribution: DistributionSummary & {
76
+ type: "timed_release";
77
+ };
78
+ /** Consumer timed release participation facts. */
79
+ consumer: TimedReleaseSequenceConsumerState;
80
+ }
81
+ /**
82
+ * The timed release has issued an admission grant to this consumer.
83
+ */
84
+ export interface TimedReleaseGranted {
85
+ /** Mechanism discriminator for timed release narrowing. */
86
+ mechanism: "timed_release";
87
+ /** Phase discriminator; `claim()` is legal on the matching view. */
88
+ phase: "granted";
89
+ /** Timed release distribution facts from the journey projector. */
90
+ distribution: DistributionSummary & {
91
+ type: "timed_release";
92
+ };
93
+ /** Consumer timed release facts associated with the grant. */
94
+ consumer: TimedReleaseSequenceConsumerState;
95
+ /** Admission credential issued by the server for this timed release. */
96
+ grant: AdmissionGrant;
97
+ }
98
+ /**
99
+ * The timed release journey is terminal for this consumer.
100
+ */
101
+ export interface TimedReleaseEnded {
102
+ /** Mechanism discriminator for timed release narrowing. */
103
+ mechanism: "timed_release";
104
+ /** Phase discriminator; no timed release actions are legal after this phase. */
105
+ phase: "ended";
106
+ /** Timed release distribution facts from the journey projector. */
107
+ distribution: DistributionSummary & {
108
+ type: "timed_release";
109
+ };
110
+ /** Consumer timed release facts at the terminal outcome. */
111
+ consumer: TimedReleaseSequenceConsumerState;
112
+ /** Server-derived terminal outcome for the timed release participation. */
113
+ outcome: SequenceOutcome;
114
+ }
115
+ /**
116
+ * Timed release views exposed by `JourneyView.sequence`.
117
+ *
118
+ * The view lifecycle mirrors the timed release sequence lifecycle. Actions exist
119
+ * only on the phases where they are legal, and each action may reject if the
120
+ * server has moved the sequence before the request is handled.
121
+ */
122
+ export type TimedReleaseSequenceView = TimedReleaseScheduledView | TimedReleaseEnterableView | TimedReleaseParticipatingView | TimedReleaseGrantedView | TimedReleaseEndedView;
123
+ /**
124
+ * Scheduled timed release view rendered before entry opens.
125
+ */
126
+ export interface TimedReleaseScheduledView {
127
+ /** Phase discriminator; no timed release action is legal yet. */
128
+ phase: "scheduled";
129
+ /** Mechanism discriminator for timed release rendering. */
130
+ mechanism: "timed_release";
131
+ /** Timed release distribution facts from the journey projector. */
132
+ distribution: DistributionSummary & {
133
+ type: "timed_release";
134
+ };
135
+ /** ISO 8601 opening timestamp chosen from the distribution start/open time. */
136
+ startsAt?: string;
137
+ /** Scheduled-only waitlist actions; this attachment never grants access. */
138
+ waitlist?: WaitlistAttachmentView;
139
+ }
140
+ /**
141
+ * Enterable timed release view rendered when the consumer can enter.
142
+ */
143
+ export interface TimedReleaseEnterableView {
144
+ /** Phase discriminator; `enter()` is legal on this view. */
145
+ phase: "enterable";
146
+ /** Mechanism discriminator for timed release rendering. */
147
+ mechanism: "timed_release";
148
+ /** Open timed release distribution facts from the journey projector. */
149
+ distribution: DistributionSummary & {
150
+ type: "timed_release";
151
+ };
152
+ /** Consumer timed release facts before entry. */
153
+ consumer: TimedReleaseSequenceConsumerState;
154
+ /**
155
+ * Enter the timed release.
156
+ *
157
+ * Legal only while this view is current. The promise rejects if entry is no
158
+ * longer allowed or the server rejects the participation request.
159
+ */
160
+ enter(): Promise<void>;
161
+ }
162
+ /**
163
+ * Participating timed release view rendered after entry.
164
+ */
165
+ export interface TimedReleaseParticipatingView {
166
+ /** Phase discriminator; `leave()` and `complete()` are legal on this view. */
167
+ phase: "participating";
168
+ /** Mechanism discriminator for timed release rendering. */
169
+ mechanism: "timed_release";
170
+ /** Consumer timed release participation facts. */
171
+ consumer: TimedReleaseSequenceConsumerState;
172
+ /** Live timed release display facts, such as release timing and availability. */
173
+ display$: ReadableAtom<TimedReleaseDisplayState>;
174
+ /**
175
+ * Leave the timed release participation.
176
+ *
177
+ * Legal only while this view is current. The promise rejects if the consumer
178
+ * is no longer participating or the server rejects the leave request.
179
+ */
180
+ leave(): Promise<void>;
181
+ /**
182
+ * Complete the timed release participation.
183
+ *
184
+ * Legal only while this view is current. The promise rejects if completion is
185
+ * no longer allowed; success moves the journey to granted or ended depending
186
+ * on the server-held credential.
187
+ */
188
+ complete(): Promise<void>;
189
+ }
190
+ /**
191
+ * Granted timed release view rendered after completion exposes a credential.
192
+ */
193
+ export interface TimedReleaseGrantedView {
194
+ /** Phase discriminator; `claim()` is legal on this view. */
195
+ phase: "granted";
196
+ /** Mechanism discriminator for timed release rendering. */
197
+ mechanism: "timed_release";
198
+ /** Timed release distribution facts from the journey projector. */
199
+ distribution: DistributionSummary & {
200
+ type: "timed_release";
201
+ };
202
+ /** Consumer timed release facts associated with the grant. */
203
+ consumer: TimedReleaseSequenceConsumerState;
204
+ /** Admission credential issued by the server for this timed release. */
205
+ grant: AdmissionGrant;
206
+ /**
207
+ * Claim the timed release admission credential for handoff.
208
+ *
209
+ * Legal only while this view is current. It returns the credential
210
+ * synchronously and may throw if the journey has already left the granted
211
+ * phase.
212
+ */
213
+ claim(): AdmissionGrant;
214
+ }
215
+ /**
216
+ * Ended timed release view rendered once the journey is terminal.
217
+ */
218
+ export interface TimedReleaseEndedView {
219
+ /** Phase discriminator; no timed release action is legal after this phase. */
220
+ phase: "ended";
221
+ /** Mechanism discriminator for timed release rendering. */
222
+ mechanism: "timed_release";
223
+ /** Consumer timed release facts at the terminal outcome. */
224
+ consumer: TimedReleaseSequenceConsumerState;
225
+ /** Server-derived terminal outcome for the timed release participation. */
226
+ outcome: SequenceOutcome;
227
+ }
@@ -21,6 +21,9 @@ export interface TimedReleaseParticipation {
21
21
  timedReleaseId: string;
22
22
  enteredAt: string;
23
23
  selectedVariantId?: string;
24
+ /** Admission credential held from entry; refreshed on every status re-mint. */
25
+ admissionGrant?: string;
26
+ expiresAt?: string;
24
27
  }
25
28
  /**
26
29
  * Timed Release consumer states (from backend)
@@ -33,6 +36,13 @@ export interface TimedReleaseEnteredState {
33
36
  status: "ENTERED";
34
37
  enteredAt: string;
35
38
  selectedVariantId?: string;
39
+ /**
40
+ * Admit = enter: the admission credential is minted at entry and held from
41
+ * then on. Status reads re-mint it statelessly (a byte-different token with the
42
+ * same standing).
43
+ */
44
+ admissionGrant?: string;
45
+ expiresAt?: string;
36
46
  }
37
47
  export interface TimedReleaseCompletedState {
38
48
  status: "COMPLETED";
@@ -55,7 +65,12 @@ export interface TimedReleaseModule {
55
65
  get(timedReleaseId: string): Promise<TimedRelease>;
56
66
  enter(timedReleaseId: string, variantId?: string, options?: RequestOptions): Promise<TimedReleaseConsumerState>;
57
67
  leave(timedReleaseId: string, options?: RequestOptions): Promise<void>;
58
- complete(timedReleaseId: string, options?: RequestOptions): Promise<string | undefined>;
68
+ /**
69
+ * Admit = enter: resolves the admission credential held from entry for the
70
+ * checkout claim. No network call — the credential was minted and returned at
71
+ * entry and refreshed by status re-mints.
72
+ */
73
+ complete(timedReleaseId: string): string | undefined;
59
74
  status(timedReleaseId: string): Promise<TimedReleaseConsumerState>;
60
75
  }
61
76
  /**
@@ -35,7 +35,7 @@ export type SdkHandoffDistributionType = Exclude<SdkDistributionType, "timed_rel
35
35
  /**
36
36
  * Distribution types that participate in journey resume / participation tracking.
37
37
  */
38
- export type SdkJourneyParticipationType = SdkDistributionType;
38
+ export type SdkJourneyParticipationType = SdkDistributionEntryType;
39
39
  /**
40
40
  * Distribution types that produce admissions for journey resume.
41
41
  * Excludes `appointment` (booking is the participation; there is no admission
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * SDK version
3
3
  * This is automatically updated during the build process
4
4
  */
5
- export declare const version = "0.2.0";
5
+ export declare const version = "0.4.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.2.0";export{o as version};
1
+ const o="0.4.0";export{o as version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanfare-io/fanfare-sdk-core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Official Fanfare Browser SDK for queue, draw, auction, and appointment experiences",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -122,7 +122,7 @@
122
122
  "uuid": "^14.0.0",
123
123
  "wretch": "^2.11.0",
124
124
  "zustand": "^5.0.6",
125
- "@fanfare-io/fanfare-sdk-contracts": "0.2.0"
125
+ "@fanfare-io/fanfare-sdk-contracts": "0.4.0"
126
126
  },
127
127
  "peerDependencies": {
128
128
  "valibot": "^1.1.0",
@@ -165,7 +165,7 @@
165
165
  "vite-plugin-dts": "^4.5.4",
166
166
  "vite-tsconfig-paths": "^5.1.4",
167
167
  "vitest": "^3.2.4",
168
- "@fanfare-io/shared-models": "0.2.0"
168
+ "@fanfare-io/shared-models": "0.4.0"
169
169
  },
170
170
  "sideEffects": false,
171
171
  "keywords": [