@fanfare-io/fanfare-sdk-core 0.1.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 (170) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +44 -0
  3. package/dist/adapters/google-analytics.d.ts +12 -0
  4. package/dist/adapters/google-analytics.js +1 -0
  5. package/dist/adapters/index.d.ts +9 -0
  6. package/dist/adapters/index.js +1 -0
  7. package/dist/adapters/types.d.ts +9 -0
  8. package/dist/appointments/appointment.module.d.ts +32 -0
  9. package/dist/appointments/appointment.module.js +1 -0
  10. package/dist/appointments/index.d.ts +2 -0
  11. package/dist/appointments/public.d.ts +1 -0
  12. package/dist/appointments/public.js +1 -0
  13. package/dist/appointments/types.d.ts +47 -0
  14. package/dist/auctions/auction.module.d.ts +58 -0
  15. package/dist/auctions/auction.module.js +1 -0
  16. package/dist/auctions/index.d.ts +5 -0
  17. package/dist/auctions/public.d.ts +5 -0
  18. package/dist/auctions/public.js +1 -0
  19. package/dist/auctions/types.d.ts +97 -0
  20. package/dist/auth/auth.module.d.ts +71 -0
  21. package/dist/auth/auth.module.js +1 -0
  22. package/dist/auth/index.d.ts +1 -0
  23. package/dist/auth/index.js +1 -0
  24. package/dist/auth/types.d.ts +112 -0
  25. package/dist/beacon/batching.d.ts +52 -0
  26. package/dist/beacon/batching.js +1 -0
  27. package/dist/beacon/beacon.module.d.ts +58 -0
  28. package/dist/beacon/beacon.module.js +1 -0
  29. package/dist/beacon/enrichment.d.ts +16 -0
  30. package/dist/beacon/enrichment.js +1 -0
  31. package/dist/beacon/index.d.ts +12 -0
  32. package/dist/beacon/public.d.ts +9 -0
  33. package/dist/beacon/public.js +1 -0
  34. package/dist/beacon/types.d.ts +103 -0
  35. package/dist/beacon/validation.d.ts +24 -0
  36. package/dist/beacon/validation.js +1 -0
  37. package/dist/challenges/challenge.module.d.ts +9 -0
  38. package/dist/challenges/challenge.module.js +1 -0
  39. package/dist/challenges/index.d.ts +3 -0
  40. package/dist/challenges/public.d.ts +9 -0
  41. package/dist/challenges/public.js +1 -0
  42. package/dist/challenges/types.d.ts +33 -0
  43. package/dist/config/index.d.ts +44 -0
  44. package/dist/config/index.js +1 -0
  45. package/dist/core/client.d.ts +15 -0
  46. package/dist/core/client.js +2 -0
  47. package/dist/core/errors.d.ts +175 -0
  48. package/dist/core/errors.js +1 -0
  49. package/dist/core/http.d.ts +87 -0
  50. package/dist/core/http.js +1 -0
  51. package/dist/core/logger.d.ts +46 -0
  52. package/dist/core/logger.js +1 -0
  53. package/dist/core/money.d.ts +10 -0
  54. package/dist/core/money.js +1 -0
  55. package/dist/core/parse-response.d.ts +62 -0
  56. package/dist/core/parse-response.js +1 -0
  57. package/dist/core/utils.d.ts +67 -0
  58. package/dist/core/utils.js +1 -0
  59. package/dist/draws/draw.module.d.ts +40 -0
  60. package/dist/draws/draw.module.js +1 -0
  61. package/dist/draws/index.d.ts +5 -0
  62. package/dist/draws/public.d.ts +6 -0
  63. package/dist/draws/public.js +1 -0
  64. package/dist/draws/types.d.ts +90 -0
  65. package/dist/draws/types.js +1 -0
  66. package/dist/errors.d.ts +5 -0
  67. package/dist/errors.js +1 -0
  68. package/dist/events.d.ts +5 -0
  69. package/dist/events.js +1 -0
  70. package/dist/experiences/distribution-monitor.runtime.d.ts +12 -0
  71. package/dist/experiences/distribution-monitor.runtime.js +1 -0
  72. package/dist/experiences/distribution-monitor.types.d.ts +62 -0
  73. package/dist/experiences/experience.module.d.ts +88 -0
  74. package/dist/experiences/experience.module.js +1 -0
  75. package/dist/experiences/index.d.ts +3 -0
  76. package/dist/experiences/index.js +1 -0
  77. package/dist/experiences/journey-contract.fixtures.d.ts +9 -0
  78. package/dist/experiences/journey-view.d.ts +22 -0
  79. package/dist/experiences/journey-view.js +1 -0
  80. package/dist/experiences/journey.d.ts +89 -0
  81. package/dist/experiences/journey.js +1 -0
  82. package/dist/experiences/journey.machine.d.ts +79 -0
  83. package/dist/experiences/journey.machine.js +1 -0
  84. package/dist/experiences/journey.types.d.ts +395 -0
  85. package/dist/experiences/public.d.ts +13 -0
  86. package/dist/experiences/public.js +1 -0
  87. package/dist/experiences/types.d.ts +161 -0
  88. package/dist/handoff/handoff.module.d.ts +184 -0
  89. package/dist/handoff/handoff.module.js +1 -0
  90. package/dist/handoff/index.d.ts +5 -0
  91. package/dist/handoff/index.js +1 -0
  92. package/dist/index.d.ts +11 -0
  93. package/dist/index.js +1 -0
  94. package/dist/internals.d.ts +11 -0
  95. package/dist/internals.js +1 -0
  96. package/dist/queues/index.d.ts +5 -0
  97. package/dist/queues/index.js +1 -0
  98. package/dist/queues/qualitative-bucket.d.ts +12 -0
  99. package/dist/queues/qualitative-bucket.js +1 -0
  100. package/dist/queues/queue.module.d.ts +42 -0
  101. package/dist/queues/queue.module.js +1 -0
  102. package/dist/queues/types.d.ts +112 -0
  103. package/dist/queues/types.js +1 -0
  104. package/dist/security/admission-proof.d.ts +15 -0
  105. package/dist/security/admission-proof.js +1 -0
  106. package/dist/state/capability-token-registry.d.ts +44 -0
  107. package/dist/state/capability-token-registry.js +1 -0
  108. package/dist/state/events.d.ts +342 -0
  109. package/dist/state/events.js +1 -0
  110. package/dist/state/store.d.ts +48 -0
  111. package/dist/state/store.js +1 -0
  112. package/dist/sync/broadcast-transport.d.ts +19 -0
  113. package/dist/sync/broadcast-transport.js +1 -0
  114. package/dist/sync/cross-tab-coordinator.d.ts +44 -0
  115. package/dist/sync/cross-tab-coordinator.js +1 -0
  116. package/dist/sync/index.d.ts +8 -0
  117. package/dist/sync/localstorage-transport.d.ts +32 -0
  118. package/dist/sync/localstorage-transport.js +1 -0
  119. package/dist/sync/protocol-transport.d.ts +51 -0
  120. package/dist/sync/protocol-transport.js +1 -0
  121. package/dist/sync/protocol.d.ts +254 -0
  122. package/dist/sync/protocol.js +1 -0
  123. package/dist/sync/recovery.d.ts +68 -0
  124. package/dist/sync/transport-factory.d.ts +16 -0
  125. package/dist/sync/transport-factory.js +1 -0
  126. package/dist/sync/transport-interface.d.ts +33 -0
  127. package/dist/sync/transport.d.ts +49 -0
  128. package/dist/sync/transport.js +1 -0
  129. package/dist/test-utils/harness-scenarios.d.ts +71 -0
  130. package/dist/test-utils/harness-scenarios.js +1 -0
  131. package/dist/test-utils/index.d.ts +12 -0
  132. package/dist/test-utils/index.js +1 -0
  133. package/dist/test-utils/mock-journey.d.ts +63 -0
  134. package/dist/test-utils/mock-journey.js +1 -0
  135. package/dist/test-utils/mock-sdk-vitest.d.ts +80 -0
  136. package/dist/test-utils/mock-sdk-vitest.js +1 -0
  137. package/dist/test-utils/mock-sdk.d.ts +22 -0
  138. package/dist/test-utils/mock-sdk.js +1 -0
  139. package/dist/test-utils/mock-server.d.ts +105 -0
  140. package/dist/test-utils/mock-server.js +1 -0
  141. package/dist/test-utils/sdk-factory.d.ts +3 -0
  142. package/dist/test-utils/sdk-factory.js +1 -0
  143. package/dist/test-utils/verification-scenarios.d.ts +43 -0
  144. package/dist/test-utils/verification-scenarios.js +1 -0
  145. package/dist/test-utils/verification-state.d.ts +1 -0
  146. package/dist/test-utils/verification-state.js +1 -0
  147. package/dist/theme.d.ts +8 -0
  148. package/dist/theme.js +1 -0
  149. package/dist/timed-releases/index.d.ts +6 -0
  150. package/dist/timed-releases/public.d.ts +6 -0
  151. package/dist/timed-releases/public.js +1 -0
  152. package/dist/timed-releases/timed-release.module.d.ts +31 -0
  153. package/dist/timed-releases/timed-release.module.js +1 -0
  154. package/dist/timed-releases/types.d.ts +70 -0
  155. package/dist/timed-releases/types.js +1 -0
  156. package/dist/types/distribution-type.d.ts +45 -0
  157. package/dist/types/index.d.ts +178 -0
  158. package/dist/utils/fingerprint.module.d.ts +27 -0
  159. package/dist/utils/fingerprint.module.js +1 -0
  160. package/dist/utils/index.d.ts +4 -0
  161. package/dist/version.d.ts +5 -0
  162. package/dist/version.js +1 -0
  163. package/dist/waitlists/index.d.ts +2 -0
  164. package/dist/waitlists/public.d.ts +6 -0
  165. package/dist/waitlists/public.js +1 -0
  166. package/dist/waitlists/types.d.ts +50 -0
  167. package/dist/waitlists/types.js +1 -0
  168. package/dist/waitlists/waitlist.module.d.ts +17 -0
  169. package/dist/waitlists/waitlist.module.js +1 -0
  170. package/package.json +205 -0
@@ -0,0 +1,90 @@
1
+ import { RequestOptions } from '../core/http';
2
+ /**
3
+ * Known draw deny reasons.
4
+ *
5
+ * When a consumer is denied entry to a draw, the `reason` field
6
+ * will contain one of these values (or a custom reason).
7
+ */
8
+ export declare const DrawDenyReason: {
9
+ /** Consumer has exceeded their order limit for this experience */
10
+ readonly ORDER_LIMIT_EXCEEDED: "ORDER_LIMIT_EXCEEDED";
11
+ /** Draw has reached maximum entries */
12
+ readonly CAPACITY_EXCEEDED: "CAPACITY_EXCEEDED";
13
+ /** Consumer validation failed */
14
+ readonly VALIDATION_FAILED: "VALIDATION_FAILED";
15
+ /** Consumer is not part of the required audience */
16
+ readonly AUDIENCE_MISMATCH: "AUDIENCE_MISMATCH";
17
+ /** Draw is closed */
18
+ readonly DRAW_CLOSED: "DRAW_CLOSED";
19
+ /** Consumer did not win the draw */
20
+ readonly NOT_SELECTED: "NOT_SELECTED";
21
+ };
22
+ export type DrawDenyReason = (typeof DrawDenyReason)[keyof typeof DrawDenyReason];
23
+ /**
24
+ * Draw participation stored in state
25
+ */
26
+ export interface DrawParticipation {
27
+ drawId: string;
28
+ enteredAt: string;
29
+ entryNumber?: string;
30
+ metadata?: Record<string, unknown>;
31
+ }
32
+ /**
33
+ * Draw result details
34
+ */
35
+ export interface DrawResult {
36
+ won: boolean;
37
+ drawTime: string;
38
+ prizeDetails?: Record<string, unknown>;
39
+ nextSteps?: string;
40
+ }
41
+ /**
42
+ * Draw consumer states (from backend)
43
+ */
44
+ export type DrawConsumerState = DrawNotEnteredState | DrawEnteredState | DrawWonState | DrawCompletedState | DrawDeniedState;
45
+ export interface DrawNotEnteredState {
46
+ status: "NOT_ENTERED";
47
+ }
48
+ export interface DrawEnteredState {
49
+ status: "ENTERED";
50
+ }
51
+ export interface DrawWonState {
52
+ status: "WON";
53
+ wonAt: string;
54
+ admissionGrant: string;
55
+ fingerprint?: string;
56
+ expiresAt?: string;
57
+ }
58
+ export interface DrawCompletedState {
59
+ status: "COMPLETED";
60
+ completedAt: string;
61
+ admissionGrant: string;
62
+ fingerprint?: string;
63
+ expiresAt?: string;
64
+ }
65
+ export interface DrawDeniedState {
66
+ status: "DENIED";
67
+ reason: string;
68
+ deniedAt: string;
69
+ }
70
+ /**
71
+ * Draw module interface
72
+ */
73
+ export interface DrawModule {
74
+ get(drawId: string): Promise<Draw>;
75
+ enter(drawId: string, metadata?: Record<string, unknown>, options?: RequestOptions): Promise<DrawConsumerState>;
76
+ leave(drawId: string, options?: RequestOptions): Promise<void>;
77
+ status(drawId: string): Promise<DrawConsumerState>;
78
+ }
79
+ /**
80
+ * Draw entity
81
+ */
82
+ export interface Draw {
83
+ id: string;
84
+ openAt?: string | null;
85
+ closeAt?: string | null;
86
+ timeZone: string;
87
+ drawAt: string;
88
+ capacity?: number | null;
89
+ continueSelectingUntilCompleted?: boolean | null;
90
+ }
@@ -0,0 +1 @@
1
+ const E={ORDER_LIMIT_EXCEEDED:"ORDER_LIMIT_EXCEEDED",CAPACITY_EXCEEDED:"CAPACITY_EXCEEDED",VALIDATION_FAILED:"VALIDATION_FAILED",AUDIENCE_MISMATCH:"AUDIENCE_MISMATCH",DRAW_CLOSED:"DRAW_CLOSED",NOT_SELECTED:"NOT_SELECTED"};export{E as DrawDenyReason};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public error surface. `classifyHttpError` is an internal helper and is not exported here.
3
+ */
4
+ export { ErrorCodes, FanfareError, ProtocolError, createError, isCapabilityGrantError, isCapabilityGrantRerouteError, isCapabilityTokenError, isCapabilityTokenRerouteError, isFanfareError, isProtocolError, } from './core/errors';
5
+ export type { ErrorCode } from './core/errors';
package/dist/errors.js ADDED
@@ -0,0 +1 @@
1
+ import{ErrorCodes as r,FanfareError as o,ProtocolError as e,createError as m,isCapabilityGrantError as p,isCapabilityGrantRerouteError as s,isCapabilityTokenError as t,isCapabilityTokenRerouteError as c,isFanfareError as f,isProtocolError as i}from"./core/errors.js";export{r as ErrorCodes,o as FanfareError,e as ProtocolError,m as createError,p as isCapabilityGrantError,s as isCapabilityGrantRerouteError,t as isCapabilityTokenError,c as isCapabilityTokenRerouteError,f as isFanfareError,i as isProtocolError};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public event type surface: the SDK event maps {@link SDKEvents} and {@link JourneyEvents}.
3
+ * The runtime `EventBus`/`getEventBus` internals in `state/events.ts` are not exported here.
4
+ */
5
+ export type { JourneyEvents, SDKEvents } from './state/events';
package/dist/events.js ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,12 @@
1
+ import { WritableAtom } from 'nanostores';
2
+ import { DistributionDisplayState, MonitorUpdate } from './distribution-monitor.types';
3
+ export declare class DistributionMonitorRuntime {
4
+ private callbacks;
5
+ private displayAtoms;
6
+ start(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void): void;
7
+ stop(id: string): void;
8
+ has(id: string): boolean;
9
+ getDisplayAtom(id: string): WritableAtom<DistributionDisplayState> | undefined;
10
+ notify(id: string, update: MonitorUpdate, once?: boolean): void;
11
+ clear(): void;
12
+ }
@@ -0,0 +1 @@
1
+ class s{constructor(){this.callbacks=/* @__PURE__ */new Map,this.displayAtoms=/* @__PURE__ */new Map}start(s,t,a){a&&this.callbacks.set(s,a),t?.displayAtom&&this.displayAtoms.set(s,t.displayAtom)}stop(s){this.callbacks.delete(s),this.displayAtoms.delete(s)}has(s){return this.callbacks.has(s)||this.displayAtoms.has(s)}getDisplayAtom(s){return this.displayAtoms.get(s)}notify(s,t,a=!0){const l=this.callbacks.get(s);l&&(l(t),a&&this.callbacks.delete(s))}clear(){this.callbacks.clear(),this.displayAtoms.clear()}}export{s as DistributionMonitorRuntime};
@@ -0,0 +1,62 @@
1
+ import { SequenceOutcome } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
2
+ export type MonitorUpdate = {
3
+ type: "granted";
4
+ token: string;
5
+ expiresAt?: number;
6
+ } | {
7
+ type: "ended";
8
+ outcome: SequenceOutcome;
9
+ };
10
+ export interface DistributionMonitor {
11
+ startMonitoring(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void): void;
12
+ stopMonitoring(id: string): void;
13
+ isMonitoring(id: string): boolean;
14
+ }
15
+ export interface QueueDisplayState {
16
+ position?: number;
17
+ estimatedWaitTime?: number;
18
+ positionDisplayMode?: "precise" | "qualitative";
19
+ }
20
+ export interface DrawDisplayState {
21
+ drawTime?: string;
22
+ entryNumber?: string;
23
+ }
24
+ export interface AuctionDisplayState {
25
+ highestBid?: string;
26
+ currentBid?: string;
27
+ bidCount?: number;
28
+ closeAt?: string;
29
+ status?: string;
30
+ currencyCode?: string;
31
+ minNextBid?: string;
32
+ bidIncrement?: string;
33
+ }
34
+ export interface TimedReleaseDisplayState {
35
+ enteredAt?: string;
36
+ checkoutUrl?: string;
37
+ }
38
+ export interface AppointmentDisplayState {
39
+ booking?: {
40
+ slotId: string;
41
+ locationId?: string;
42
+ locationName?: string;
43
+ /** Join URL for online appointments. Absent for in-person bookings. */
44
+ joinUrl?: string;
45
+ startTime: string;
46
+ endTime: string;
47
+ confirmationCode?: string;
48
+ consumerStatus: "booked" | "checked_in" | "completed";
49
+ };
50
+ msUntilStart?: number;
51
+ }
52
+ export type DistributionDisplayState = (QueueDisplayState & {
53
+ type: "queue";
54
+ }) | (DrawDisplayState & {
55
+ type: "draw";
56
+ }) | (AuctionDisplayState & {
57
+ type: "auction";
58
+ }) | (TimedReleaseDisplayState & {
59
+ type: "timed_release";
60
+ }) | (AppointmentDisplayState & {
61
+ type: "appointment";
62
+ });
@@ -0,0 +1,88 @@
1
+ import { AuctionModule } from '../auctions/types';
2
+ import { HttpClient } from '../core/http';
3
+ import { DrawModule } from '../draws/types';
4
+ import { QueueModule } from '../queues/types';
5
+ import { TimedReleaseModule } from '../timed-releases/types';
6
+ import { WaitlistModule } from '../waitlists/types';
7
+ import { DistributionContext, ExperienceDetails, ExperienceModule, ExperienceSession, RoutingResult, Sequence, SequenceAccessResult } from './types';
8
+ /**
9
+ * Custom errors for experience operations
10
+ */
11
+ export declare class NoSequenceAvailableError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ export declare class SequenceFullError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ /**
18
+ * Experience Management Module
19
+ * Orchestrates experience-centric customer journeys and delegates to distribution modules
20
+ */
21
+ export declare class ExperienceManagementModule implements ExperienceModule {
22
+ private readonly http;
23
+ private readonly logger;
24
+ private readonly events;
25
+ private get store();
26
+ private readonly queues;
27
+ private readonly draws;
28
+ private readonly auctions;
29
+ private readonly waitlists;
30
+ private readonly timedReleases;
31
+ constructor(http: HttpClient, queues: QueueModule, draws: DrawModule, auctions: AuctionModule, waitlists: WaitlistModule, timedReleases: TimedReleaseModule);
32
+ /**
33
+ * Get experience details
34
+ */
35
+ get(experienceId: string): Promise<ExperienceDetails>;
36
+ /**
37
+ * Enter an experience and start a session
38
+ */
39
+ enter(experienceId: string): Promise<ExperienceSession>;
40
+ /**
41
+ * Leave an experience and clean up state
42
+ */
43
+ leave(experienceId: string): Promise<void>;
44
+ /** Map a server routing gate to the SDK's {@link RoutingGate}. */
45
+ private mapGate;
46
+ /** Map a routing candidate sequence to a {@link Sequence}. */
47
+ private mapSequence;
48
+ /** Map a validated routing decision into the SDK's {@link RoutingResult}. */
49
+ private mapRoutingResult;
50
+ /** Find eligible sequences for an experience. */
51
+ findSequence(experienceId: string, accessCode?: string): Promise<RoutingResult>;
52
+ /**
53
+ * Validate access to a specific sequence
54
+ */
55
+ validateSequenceAccess(sequenceId: string, accessCode?: string): Promise<SequenceAccessResult>;
56
+ /**
57
+ * Select and assign a sequence to the user
58
+ */
59
+ selectSequence(sequenceId: string): Promise<void>;
60
+ /**
61
+ * Get current distribution context with timing awareness
62
+ */
63
+ getCurrentDistributions(sequenceId: string): Promise<DistributionContext>;
64
+ /**
65
+ * Enter an active distribution by delegating to appropriate module
66
+ */
67
+ enterDistribution(distribution: NonNullable<DistributionContext["active"]>): Promise<void>;
68
+ /**
69
+ * Get current active experience session
70
+ */
71
+ getActiveSession(): ExperienceSession | null;
72
+ /**
73
+ * Check if user is in a specific experience
74
+ */
75
+ isInExperience(experienceId: string): boolean;
76
+ /**
77
+ * Get currently assigned sequence
78
+ */
79
+ getSelectedSequence(): Sequence | null;
80
+ /**
81
+ * Fetch authenticated consumer participations (used for journey resume)
82
+ */
83
+ getMe(): Promise<import('./types').ConsumerMe>;
84
+ /**
85
+ * Clean up module resources
86
+ */
87
+ destroy(): void;
88
+ }
@@ -0,0 +1 @@
1
+ import{ConsumerMeResponseSchema as e}from"@fanfare-io/fanfare-sdk-contracts/consumer-me";import{RoutingDecisionV1Schema as t}from"@fanfare-io/fanfare-sdk-contracts/routing";import{createError as r}from"../core/errors.js";import{getLogger as s}from"../core/logger.js";import{parseResponse as i}from"../core/parse-response.js";import{getEventBus as n}from"../state/events.js";import{getSDKStore as c}from"../state/store.js";class a extends Error{constructor(e){super(e),this.name="NoSequenceAvailableError"}}class o extends Error{constructor(e){super(e),this.name="SequenceFullError"}}class u{constructor(e,t,r,i,c,a){this.logger=s(),this.events=n(),this.http=e,this.queues=t,this.draws=r,this.auctions=i,this.waitlists=c,this.timedReleases=a}get store(){return c()}async get(e){try{return this.logger.info("Getting experience details",{experienceId:e}),await this.http.get(`/experiences/${e}`)}catch(t){throw this.logger.error("Failed to get experience",{experienceId:e,error:t}),this.events.emit("experience:error",{experienceId:e,error:t instanceof Error?t.message:"Unknown error"}),t}}async enter(e){try{const t=this.store.activeExperiences[e];if(t)return this.logger.info("Already in experience",{experienceId:e}),t;if(!this.store.session)throw r.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");this.logger.info("Entering experience",{experienceId:e});const s=await this.http.post(`/experiences/${e}/enter`);return this.store.addActiveExperience(e,s),this.events.emit("experience:entered",{experienceId:e,enteredAt:s.enteredAt}),s}catch(t){if(t&&"object"==typeof t&&"message"in t){const r=t.message.toLowerCase();if(r.includes("already entered")||r.includes("already in experience"))return this.logger.info("Already in experience (from API)",{experienceId:e}),this.store.activeExperiences[e]||{experienceId:e,enteredAt:/* @__PURE__ */(new Date).toISOString()}}throw this.logger.error("Failed to enter experience",{experienceId:e,error:t}),this.events.emit("experience:error",{experienceId:e,error:t instanceof Error?t.message:"Unknown error"}),t}}async leave(e){try{const r=this.store.activeExperiences[e];if(!r)return void this.logger.warn("Not in experience",{experienceId:e});if(this.logger.info("Leaving experience",{experienceId:e}),r.currentDistribution){const{type:e,id:s}=r.currentDistribution;try{switch(e){case"queue":await this.queues.leave(s);break;case"draw":await this.draws.leave(s)}}catch(t){this.logger.warn("Failed to leave distribution during experience leave",{type:e,id:s,error:t})}}if(r.waitlistId)try{await this.waitlists.leave(r.waitlistId)}catch(t){this.logger.warn("Failed to leave waitlist during experience leave",{waitlistId:r.waitlistId,error:t})}await this.http.delete(`/experiences/${e}/leave`),this.store.removeActiveExperience(e),this.events.emit("experience:left",{experienceId:e})}catch(t){throw this.logger.error("Failed to leave experience",{experienceId:e,error:t}),this.events.emit("experience:error",{experienceId:e,error:t instanceof Error?t.message:"Unknown error"}),t}}mapGate(e){switch(e.type){case"authentication":return{type:"authentication",status:"required"};case"access_code":return{type:"access_code",status:e.status};case"bot_check":return{type:"bot_check",status:e.status,challenge:e.challenge,mitigation:e.mitigation}}}mapSequence(e){return{id:e.id,name:e.name,experienceId:e.experienceId,priority:e.priority,color:e.color??""}}mapRoutingResult(e){const t=e.offers.filter(e=>"gated"===e.status).map(e=>({sequence:this.mapSequence(e.sequence),status:"gated",gates:e.gates.map(e=>this.mapGate(e))}));if("no_access"===e.outcome)return{outcome:"no_access",offers:t};const r=e.selected;return{outcome:e.outcome,selected:{sequence:this.mapSequence(r.sequence),status:r.status,gates:r.gates.map(e=>this.mapGate(e)),capabilityGrant:r.sequenceCapabilityGrant,capabilityGrantExpiresAt:r.sequenceCapabilityGrantExpiresAt},offers:t}}async findSequence(e,r){try{this.logger.info("Finding sequence",{experienceId:e,hasAccessCode:!!r});const s=await this.http.post(`/experiences/${e}/route`,r?{accessCode:r}:{}),n=i(t,s,{endpoint:`/experiences/${e}/route`},{throwOnFailure:!0});if(!n.ok)throw n.error;return this.mapRoutingResult(n.value)}catch(s){throw this.logger.error("Failed to find sequence",{experienceId:e,error:s}),s}}async validateSequenceAccess(e,t){try{this.logger.info("Validating sequence access",{sequenceId:e,hasAccessCode:!!t});const r=await this.http.post(`/sequences/${e}/validate`,t?{accessCode:t}:{});return{sequenceId:e,hasAccess:r.hasAccess,accessReasons:r.accessReasons,deniedReasons:r.deniedReasons}}catch(r){throw this.logger.error("Failed to validate sequence access",{sequenceId:e,error:r}),r}}async selectSequence(e){try{this.logger.info("Selecting sequence",{sequenceId:e});const t=Object.keys(this.store.activeExperiences)[0];if(!t)throw r.internalError("No active experience found for sequence selection");this.store.updateExperienceSession(t,{assignedSequenceId:e}),this.events.emit("experience:sequence-selected",{sequenceId:e})}catch(t){if(t&&"object"==typeof t&&"message"in t){const e=t.message.toLowerCase();if(e.includes("sequence full")||e.includes("capacity reached"))throw new o("Sequence is full")}throw this.logger.error("Failed to select sequence",{sequenceId:e,error:t}),t}}async getCurrentDistributions(e){try{this.logger.info("Getting distribution context",{sequenceId:e});return await this.http.get(`/sequences/${e}/distributions`)}catch(t){throw this.logger.error("Failed to get distribution context",{sequenceId:e,error:t}),t}}async enterDistribution(e){try{this.logger.info("Entering distribution",{type:e.type,id:e.id});const t=c(),r=Object.keys(t.activeExperiences)[0],s=r?t.activeExperiences[r]:null;let i;switch(e.type){case"queue":{const t=await this.queues.enter(e.id);i="DENIED"===t.status||"EXPIRED"===t.status||"LEFT"===t.status?"ended":"active";break}case"draw":{const t=await this.draws.enter(e.id);i="DENIED"===t.status||"NOT_ENTERED"===t.status?"ended":"active";break}case"auction":await this.auctions.enter(e.id),i="active";break;case"appointment":this.logger.warn("Appointment distribution not yet implemented",{id:e.id});break;case"timed_release":i="ENTERED"===(await this.timedReleases.enter(e.id)).status?"active":"ended";break;default:this.logger.warn("Unknown distribution type",{type:e.type})}s&&r&&t.updateExperienceSession(r,{currentDistribution:i?{type:e.type,id:e.id,status:i}:void 0})}catch(t){throw this.logger.error("Failed to enter distribution",{type:e.type,id:e.id,error:t}),t}}getActiveSession(){const e=Object.values(this.store.activeExperiences);return e.length>0?e[0]:null}isInExperience(e){return!!this.store.activeExperiences[e]}getSelectedSequence(){const e=this.getActiveSession();return e&&e.assignedSequenceId?{id:e.assignedSequenceId,name:"",experienceId:e.experienceId,priority:0,color:"#000000"}:null}async getMe(){const t=await this.http.get("/consumers/me"),r=i(e,t,{endpoint:"/consumers/me"},{throwOnFailure:!0});if(!r.ok)throw r.error;return r.value}destroy(){this.logger.debug("ExperienceManagementModule destroyed")}}export{u as ExperienceManagementModule,a as NoSequenceAvailableError,o as SequenceFullError};
@@ -0,0 +1,3 @@
1
+ export { ExperienceManagementModule, NoSequenceAvailableError, SequenceFullError } from './experience.module';
2
+ export { ExperienceJourney } from './journey';
3
+ export * from './types';
@@ -0,0 +1 @@
1
+ import{ExperienceManagementModule as e,NoSequenceAvailableError as o,SequenceFullError as r}from"./experience.module.js";import{ExperienceJourney as m}from"./journey.js";export{m as ExperienceJourney,e as ExperienceManagementModule,o as NoSequenceAvailableError,r as SequenceFullError};
@@ -0,0 +1,9 @@
1
+ export declare function canonicalReadySnapshot(experienceId?: string): import('./journey.types').ReadySnapshot;
2
+ export declare function canonicalGatedSnapshot(experienceId?: string): import('./journey.types').GatedSnapshot;
3
+ export declare function canonicalUpcomingQueueSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
4
+ export declare function canonicalActiveSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
5
+ export declare function canonicalParticipatingAuctionSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
6
+ export declare function canonicalEnterableAuctionSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
7
+ export declare function canonicalParticipatingTimedReleaseSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
8
+ export declare function canonicalDeniedActiveSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
9
+ export declare function canonicalDeniedClosedSnapshot(experienceId?: string): import('./journey.types').RoutedSnapshot;
@@ -0,0 +1,22 @@
1
+ import { ReadableAtom } from 'nanostores';
2
+ import { DistributionDisplayState } from './distribution-monitor.types';
3
+ import { AdmissionGrant, JourneySnapshot, JourneyView } from './journey.types';
4
+ export interface JourneyViewHandlers {
5
+ start(opts?: {
6
+ accessCode?: string;
7
+ reset?: boolean;
8
+ }): Promise<void>;
9
+ reroute(opts?: {
10
+ accessCode?: string;
11
+ }): Promise<void>;
12
+ retry(): Promise<void>;
13
+ enter(): Promise<void>;
14
+ leave(): Promise<void>;
15
+ bid(amount: string): Promise<void>;
16
+ complete(): Promise<void>;
17
+ book(slotId: string, locationId?: string): Promise<void>;
18
+ cancel(reason?: string): Promise<void>;
19
+ reschedule(newSlotId: string, newLocationId?: string): Promise<void>;
20
+ claim(): AdmissionGrant;
21
+ }
22
+ export declare function buildJourneyView(snapshot: JourneySnapshot, handlers: JourneyViewHandlers, displayAtom: ReadableAtom<DistributionDisplayState> | null): JourneyView;
@@ -0,0 +1 @@
1
+ function e(e,n,a){switch(e.journeyStage){case"ready":return{journeyStage:"ready",start:e=>n.start(e)};case"routing":return{journeyStage:"routing"};case"gated":return{journeyStage:"gated",gates:e.gates,selected:e.selected,reroute:e=>n.reroute(e),retry:()=>n.retry()};case"routed":return{journeyStage:"routed",sequenceId:e.sequenceId,offers:e.offers,reroute:()=>n.reroute(),retry:()=>n.retry(),sequence:t(e,n,a)}}}function t(e,t,n){const a=e.sequence;switch(a.phase){case"unavailable":return{phase:"unavailable",reason:a.reason};case"scheduled":return{phase:"scheduled",mechanism:a.mechanism,distribution:a.distribution,consumer:a.consumer,startsAt:a.distribution.startsAt??a.distribution.opensAt};case"enterable":return"appointment"===a.mechanism?{phase:"enterable",mechanism:"appointment",distribution:a.distribution,consumer:a.consumer,book:(e,n)=>t.book(e,n)}:"waitlist"===a.mechanism?{phase:"enterable",mechanism:"waitlist",target:a.target,consumer:a.consumer,enter:()=>t.enter()}:"auction"===a.mechanism?{phase:"enterable",mechanism:"auction",distribution:a.distribution,consumer:a.consumer,bid:e=>t.bid(e)}:{phase:"enterable",mechanism:a.mechanism,distribution:a.distribution,consumer:a.consumer,enter:()=>t.enter()};case"participating":return"waitlist"===a.mechanism?{phase:"participating",mechanism:"waitlist",consumer:a.consumer,leave:()=>t.leave()}:"appointment"===a.mechanism?{phase:"participating",mechanism:"appointment",consumer:a.consumer,state$:n,cancel:e=>t.cancel(e),reschedule:(e,n)=>t.reschedule(e,n)}:"auction"===a.mechanism?{phase:"participating",mechanism:"auction",consumer:a.consumer,state$:n,bid:e=>t.bid(e),leave:()=>t.leave()}:"timed_release"===a.mechanism?{phase:"participating",mechanism:"timed_release",consumer:a.consumer,state$:n,leave:()=>t.leave(),complete:()=>t.complete()}:"queue"===a.mechanism?{phase:"participating",mechanism:"queue",consumer:a.consumer,state$:n,leave:()=>t.leave()}:{phase:"participating",mechanism:"draw",consumer:a.consumer,state$:n,leave:()=>t.leave()};case"settling":return{phase:"settling",mechanism:a.mechanism,distribution:a.distribution,consumer:a.consumer};case"granted":return{phase:"granted",mechanism:a.mechanism,distribution:a.distribution,consumer:a.consumer,grant:a.grant,claim:()=>t.claim()};case"ended":return{phase:"ended",mechanism:a.mechanism,consumer:a.consumer,outcome:a.outcome}}throw new Error("Unknown sequence phase")}export{e as buildJourneyView};
@@ -0,0 +1,89 @@
1
+ import { ReadableAtom, WritableAtom } from 'nanostores';
2
+ import { InternalFanfareSDK } from '../types';
3
+ import { DistributionDisplayState, MonitorUpdate } from './distribution-monitor.types';
4
+ import { JourneyEvent, JourneySnapshot, JourneyView, Participation } from './journey.types';
5
+ import { ConsumerMe } from './types';
6
+ export declare class ExperienceJourney {
7
+ private readonly experienceId;
8
+ private readonly sdk;
9
+ private readonly stateAtom;
10
+ private readonly store;
11
+ readonly state: ReadableAtom<JourneySnapshot>;
12
+ readonly view$: ReadableAtom<JourneyView>;
13
+ readonly latestEvent$: ReadableAtom<JourneyEvent | null>;
14
+ readonly events$: ReadableAtom<JourneyEvent[]>;
15
+ private pollTimer?;
16
+ private activeMonitor;
17
+ private grantExpiryTimer?;
18
+ private displayAtom;
19
+ private destroyed;
20
+ private operationQueue;
21
+ private readonly EVENT_BUFFER_LIMIT;
22
+ constructor(experienceId: string, sdk: InternalFanfareSDK);
23
+ getExperienceId(): string;
24
+ private start;
25
+ private reroute;
26
+ private retry;
27
+ destroy(): void;
28
+ resumeFromMe(me: ConsumerMe): Promise<boolean>;
29
+ refreshDistribution(): Promise<void>;
30
+ private enterDistributionInternal;
31
+ private enterActiveDistribution;
32
+ private bookAppointmentSlot;
33
+ private cancelAppointmentBooking;
34
+ private rescheduleAppointmentBooking;
35
+ private completeTimedRelease;
36
+ private bid;
37
+ private enterWait;
38
+ private leaveWait;
39
+ private enter;
40
+ private leave;
41
+ private leaveParticipation;
42
+ private claim;
43
+ ackEvent(eventId: string): void;
44
+ ackAllEvents(): void;
45
+ private routeAndLoadDistribution;
46
+ private resolveRouting;
47
+ private enterExperienceIfReady;
48
+ private buildView;
49
+ private mutate;
50
+ private flattenSnapshot;
51
+ private emitEvent;
52
+ private emitIfDistributionChanged;
53
+ private buildDistributionSummary;
54
+ private normalizeOffers;
55
+ private restoreFromStore;
56
+ private persist;
57
+ private setSnapshot;
58
+ private emitPhaseChanged;
59
+ private runSerializedOperation;
60
+ private transitionToParticipating;
61
+ private transitionToGranted;
62
+ private transitionToEnded;
63
+ applyMonitorUpdate(update: MonitorUpdate): void;
64
+ private startMonitorForParticipation;
65
+ private getDistributionModule;
66
+ stopRuntimeMonitoring(clearDisplay?: boolean): void;
67
+ private clearGrantExpiryTimer;
68
+ private rearmTimersIfNeeded;
69
+ getRuntimeBinding(): {
70
+ experienceId: string;
71
+ sequenceId: string;
72
+ participationId: string;
73
+ participationType: Exclude<Participation["type"], "waitlist">;
74
+ } | null;
75
+ startRuntimeMonitoring(): void;
76
+ getRuntimeDisplayAtom(): WritableAtom<DistributionDisplayState> | null;
77
+ applyRuntimeState(state: DistributionDisplayState | null): void;
78
+ reconcileSyncedSnapshot(snapshot: JourneySnapshot | null): void;
79
+ private ensureDisplayAtom;
80
+ private resetDisplayAtom;
81
+ private clearRuntimeDisplayState;
82
+ private enteredConsumer;
83
+ private mapEnterResultToUpdate;
84
+ private queueOutcome;
85
+ private deriveTerminalOutcomeForClosedDistribution;
86
+ private withGrantedConsumer;
87
+ private withTerminalConsumer;
88
+ private stopPolling;
89
+ }
@@ -0,0 +1 @@
1
+ import{atom as e,computed as t}from"nanostores";import{createError as i}from"../core/errors.js";import{isMoneyString as s,addMoney as n}from"../core/money.js";import{generateId as o}from"../core/utils.js";import{clearExperienceContext as r,clearSequenceContext as a,bindDistributionContext as d,upsertSequenceToken as c}from"../state/capability-token-registry.js";import{getEventBus as u}from"../state/events.js";import{getSDKStore as p}from"../state/store.js";import{NoSequenceAvailableError as h}from"./experience.module.js";import{buildJourneyView as m}from"./journey-view.js";import{createInitialSnapshot as l,getParticipation as y,getSequencePhase as g,normalizeSequenceState as I,getSequenceId as f,getDistribution as w,getConsumer as v,getOutcome as b,deriveSequenceState as A,getGrant as E,getSequenceState as q,getTarget as x,getGates as T,buildSnapshot as S,getOffers as D,createGrant as k,getAdmissionGrantExpiresAt as C}from"./journey.machine.js";class j{constructor(i,s){this.experienceId=i,this.sdk=s,this.store=p(),this.activeMonitor=null,this.displayAtom=null,this.destroyed=!1,this.operationQueue=Promise.resolve(),this.EVENT_BUFFER_LIMIT=20;const n=this.restoreFromStore(),o=n??l(this.experienceId),r=n?y(n):void 0;r&&"participating"===g(o)&&this.ensureDisplayAtom(r),this.stateAtom=e(o),this.state=this.stateAtom,this.view$=t(this.stateAtom,e=>this.buildView(e)),this.events$=t(this.stateAtom,e=>e.events),this.latestEvent$=t(this.stateAtom,e=>{if(!e.events.length)return null;const t=e.lastSeenEventId??"",i=e.events.filter(e=>e.id>t);return i.length?i[i.length-1]:null}),n?this.rearmTimersIfNeeded():this.persist()}getExperienceId(){return this.experienceId}async start(e){await this.runSerializedOperation(async()=>{e?.reset&&this.setSnapshot(l(this.experienceId)),"accessCode"in(e??{})&&this.mutate({accessCode:e?.accessCode??null}),await this.routeAndLoadDistribution(this.stateAtom.get().accessCode)})}async reroute(e={}){await this.runSerializedOperation(async()=>{"accessCode"in e&&this.mutate({accessCode:e.accessCode??null}),await this.routeAndLoadDistribution(e.accessCode??this.stateAtom.get().accessCode)})}async retry(){await this.runSerializedOperation(()=>this.routeAndLoadDistribution(this.stateAtom.get().accessCode))}destroy(){this.destroyed||(this.destroyed=!0,this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.stopPolling(),r(this.experienceId),this.store.removeJourney(this.experienceId),this.stateAtom.set(l(this.experienceId)))}async resumeFromMe(e){const t=e.active.journeys.find(e=>e.experienceId===this.experienceId&&"routed"===e.journeyStage);if(!t||"routed"!==t.journeyStage)return!1;const i=this.stateAtom.get(),s=I({sequence:t.sequence});this.mutate({journeyStage:"routed",sequenceId:t.sequenceId,sequence:s,offers:this.normalizeOffers(t.offers)}),"routed"===i.journeyStage&&i.sequenceId!==t.sequenceId&&a(this.experienceId,i.sequenceId);try{await this.reroute()}catch{}return this.rearmTimersIfNeeded(),!0}async refreshDistribution(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=f(e);if(!t)throw i.validationError("Cannot refresh distribution before routing to a sequence");const s=await this.sdk.experiences.getCurrentDistributions(t),n=this.buildDistributionSummary(s,t),o=w(e);n&&d({experienceId:this.experienceId,sequenceId:t,distributionContext:n});const r=v(e);let a="open"===n?.lifecycle&&"waitlist"===r?.mechanism?void 0:r;const c=this.deriveTerminalOutcomeForClosedDistribution(a,n,b(e));a=this.withTerminalConsumer(a,c);const u=A({distribution:n,consumer:a,grant:E(e),outcome:c});this.mutate({sequence:u,distribution:n,consumer:a,outcome:c}),this.emitIfDistributionChanged(o,n)})}async enterDistributionInternal(e){const t=this.stateAtom.get(),s=w(t);if(!s||"open"!==s.lifecycle||s.type!==e)throw i.validationError(`No open ${e.replace(/_/g," ")} to enter`);const n={queue:this.sdk.queues,draw:this.sdk.draws,auction:this.sdk.auctions,timed_release:this.sdk.timedReleases},o=await n[e].enter(s.id,void 0),r=this.mapEnterResultToUpdate(e,o);if(r)return void this.applyMonitorUpdate(r);const a=this.enteredConsumer(e,s.id);"auction"===e&&await this.sdk.auctions.status(s.id),this.ensureDisplayAtom({id:s.id,type:e}),this.transitionToParticipating(a,s),this.emitEvent("sequence_change","success","user",`Entered ${e.replace(/_/g," ")}`,{distributionId:s.id})}async enterActiveDistribution(){await this.runSerializedOperation(async()=>{const e=w(this.stateAtom.get());if(!e||"open"!==e.lifecycle)throw i.validationError("No open distribution to enter");if("appointment"===e.type)throw i.validationError("Appointments are entered via bookAppointmentSlot()");await this.enterDistributionInternal(e.type)})}async bookAppointmentSlot(e,t){await this.runSerializedOperation(async()=>{const s=w(this.stateAtom.get());if(!s||"open"!==s.lifecycle||"appointment"!==s.type)throw i.validationError("No open appointment to book");const n=await this.sdk.appointments.book(s.id,e,t);this.sdk.appointments.bumpMonitorGeneration(s.id);const o=this.ensureDisplayAtom({id:s.id,type:"appointment"}),r=Math.max(0,Date.parse(n.startTime)-Date.now());o.set({type:"appointment",booking:{slotId:n.slotId,locationId:n.locationId,locationName:n.locationName,joinUrl:n.joinUrl,startTime:n.startTime,endTime:n.endTime,confirmationCode:n.confirmationCode,consumerStatus:n.consumerStatus},msUntilStart:r});const a={mechanism:"appointment",status:"booked",id:s.id,distributionId:s.id,slotId:n.slotId,locationId:n.locationId};this.transitionToParticipating(a,s),this.emitEvent("sequence_change","success","user","Booked appointment slot",{appointmentId:s.id,slotId:e,locationId:t})})}async cancelAppointmentBooking(e){await this.runSerializedOperation(async()=>{const t=this.stateAtom.get(),s=v(t);if("appointment"!==s?.mechanism)throw i.validationError("Not participating in an appointment");const n=this.displayAtom?.get(),o=n&&"appointment"===n.type?n.booking:void 0;if(!o)throw i.validationError("No booking details available to cancel");this.stopRuntimeMonitoring(!0),await this.sdk.appointments.cancel(s.distributionId??s.id??"",o.slotId,o.locationId??"",e),this.transitionToEnded({type:"cancelled",reason:e})})}async rescheduleAppointmentBooking(e,t){await this.runSerializedOperation(async()=>{const s=v(this.stateAtom.get());if("appointment"!==s?.mechanism)throw i.validationError("Not participating in an appointment");const n=await this.sdk.appointments.reschedule(s.distributionId??s.id??"",e,t);this.sdk.appointments.bumpMonitorGeneration(s.distributionId??s.id??"");const o=this.ensureDisplayAtom({id:s.distributionId??s.id??"",type:"appointment"}),r=Math.max(0,Date.parse(n.startTime)-Date.now());o.set({type:"appointment",booking:{slotId:n.slotId,locationId:n.locationId,locationName:n.locationName,joinUrl:n.joinUrl,startTime:n.startTime,endTime:n.endTime,confirmationCode:n.confirmationCode,consumerStatus:n.consumerStatus},msUntilStart:r}),this.emitEvent("sequence_change","info","user","Rescheduled appointment",{appointmentId:s.distributionId??s.id,newSlotId:e,newLocationId:t})})}async completeTimedRelease(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=v(e);if("timed_release"!==t?.mechanism||"entered"!==t.status)throw i.validationError("Not participating in a timed release");const s=await this.sdk.timedReleases.complete(t.distributionId??t.id??"");s?this.transitionToGranted(s):this.transitionToEnded({type:"completed"}),this.emitEvent("sequence_change","success","user","Completed timed release",{timedReleaseId:t.distributionId??t.id})})}async bid(e){await this.runSerializedOperation(async()=>{const t=this.stateAtom.get(),o=v(t);if("auction"!==o?.mechanism)throw i.validationError("No auction is available to bid on");const r=w(t),a=q(t),d=o.distributionId??o.id??r?.id??"",c=await this.sdk.auctions.bid(d,e),u="winning"===c.status?"winning":"outbid"===c.status?"outbid":"bidding",p="auction"===r?.details?.type?r.details:void 0,h=p?.bidIncrement,m=c.highestBid&&h&&s(c.highestBid)&&s(h)?n(c.highestBid,h):void 0,l=this.ensureDisplayAtom({id:d,type:"auction"});"enterable"===a?.phase&&r&&this.transitionToParticipating({mechanism:"auction",status:u,id:d,distributionId:d,currentBid:e,highestBid:c.highestBid},r),l.set({type:"auction",currentBid:e,highestBid:c.highestBid,bidCount:c.bidCount,status:u,currencyCode:p?.currencyCode,bidIncrement:h,minNextBid:m,closeAt:p?.closeAt??p?.endsAt})})}async enterWait(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=f(e),s=w(e),n=x(e),o=n?.waitlistId??s?.waitlistId,r="string"==typeof o?o:void 0;if(!t||!r)throw i.validationError("Waitlist is not available for this sequence");const a={mechanism:"waitlist",status:"waitlisted",id:(await this.sdk.waitlists.enter(r)).id,waitlistId:r},d=A({distribution:s,target:n,consumer:a});this.mutate({sequence:d,consumer:a}),this.emitEvent("sequence_change","info","user","Entered waitlist",{waitlistId:r})})}async leaveWait(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=v(e),i=x(e),s="waitlist"===t?.mechanism?t.waitlistId??i?.waitlistId??t.id:void 0;if(!s)return;await this.sdk.waitlists.leave(s);const n=w(e),o=A({distribution:n,target:i});this.mutate({sequence:o}),this.emitEvent("sequence_change","info","user","Left waitlist",{waitlistId:s})})}async enter(){const e=q(this.stateAtom.get());"enterable"!==e?.phase||"waitlist"!==e.mechanism?await this.enterActiveDistribution():await this.enterWait()}async leave(){const e=v(this.stateAtom.get());"waitlist"!==e?.mechanism?await this.leaveParticipation():await this.leaveWait()}async leaveParticipation(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=y(e);t&&(this.stopRuntimeMonitoring(!0),"queue"===t.type?await this.sdk.queues.leave(t.id):"draw"===t.type?await this.sdk.draws.leave(t.id):"auction"===t.type?await this.sdk.auctions.leave(t.id):"timed_release"===t.type&&await this.sdk.timedReleases.leave(t.id),this.transitionToEnded({type:"left"}),this.emitEvent("sequence_change","info","user","Left participation",{type:t.type}))})}claim(){const e=E(this.stateAtom.get());if(!e)throw i.validationError("No grant is available to claim");return this.emitEvent("sequence_change","success","user","Grant claimed",{distributionType:e.distributionType,distributionId:e.distributionId,sequenceId:e.sequenceId}),u().emit("journey:grant-claimed",{experienceId:this.experienceId,sequenceId:e.sequenceId,grant:e.token}),e}ackEvent(e){this.stateAtom.get().events.some(t=>t.id===e)&&this.mutate({lastSeenEventId:e})}ackAllEvents(){const e=this.stateAtom.get();if(!e.events.length)return;const t=e.events[e.events.length-1];this.mutate({lastSeenEventId:t.id})}async routeAndLoadDistribution(e){const t=this.stateAtom.get(),i=f(t),s=await this.resolveRouting(e);if(!s||this.destroyed)return;if(await this.enterExperienceIfReady(),this.destroyed)return;const n=await this.sdk.experiences.getCurrentDistributions(s.sequenceId);if(this.destroyed)return;const o=this.buildDistributionSummary(n,s.sequenceId);o&&d({experienceId:this.experienceId,sequenceId:s.sequenceId,distributionContext:o});const r=i===s.sequenceId,a=r?v(t):void 0;let c="open"===o?.lifecycle&&"waitlist"===a?.mechanism?void 0:a;const u=r?E(t):void 0,p=r?this.deriveTerminalOutcomeForClosedDistribution(c,o,b(t)):void 0;c=this.withTerminalConsumer(c,p);const h=!o&&r&&"routed"===t.journeyStage&&("granted"===t.sequence.phase||"ended"===t.sequence.phase||"mechanism"in t.sequence&&"waitlist"===t.sequence.mechanism)?t.sequence:A({distribution:o,consumer:c,grant:u,outcome:p});this.mutate({journeyStage:"routed",sequenceId:s.sequenceId,distribution:o,offers:s.offers,sequence:h,consumer:c,grant:u,outcome:p});const m=r?w(t):void 0;this.emitIfDistributionChanged(m,o)}async resolveRouting(e){const t=this.stateAtom.get(),i=f(t);let s;this.mutate({journeyStage:"routing"});try{s=await this.sdk.experiences.findSequence(this.experienceId,e)}catch(o){throw this.setSnapshot(t),this.emitEvent("error","error","user","Failed to route experience"),o}if(this.destroyed)return null;if("gated"===s.outcome&&s.selected){const e=s.selected.gates;return this.mutate({journeyStage:"gated",gates:e,selected:{sequence:s.selected.sequence,status:"gated"}}),this.emitEvent("requirement","info","user","Gates required",{gates:e}),null}if("no_access"===s.outcome||!s.selected)throw this.setSnapshot(t),this.emitEvent("error","error","user","No access available for this experience"),new h("No access available for this experience");const n=s.selected.sequence.id;return i&&i!==n&&a(this.experienceId,i),s.selected.capabilityGrant&&c({experienceId:this.experienceId,sequenceId:n,token:s.selected.capabilityGrant,expiresAt:s.selected.capabilityGrantExpiresAt}),i!==n&&this.emitEvent("reroute","info","user","Sequence changed",{previousSequenceId:i,sequenceId:n}),{sequenceId:n,offers:s.offers}}async enterExperienceIfReady(){const e=this.stateAtom.get();if(T(e).length>0)return;if(e.experience)return;const t=await this.sdk.experiences.enter(this.experienceId);this.destroyed||this.mutate({experience:t})}buildView(e){return m(e,{start:e=>this.start(e),reroute:e=>this.reroute(e),retry:()=>this.retry(),enter:()=>this.enter(),leave:()=>this.leave(),bid:e=>this.bid(e),complete:()=>this.completeTimedRelease(),book:(e,t)=>this.bookAppointmentSlot(e,t),cancel:e=>this.cancelAppointmentBooking(e),reschedule:(e,t)=>this.rescheduleAppointmentBooking(e,t),claim:()=>this.claim()},this.displayAtom)}mutate(e){if(this.destroyed)return;const t=this.stateAtom.get(),i={...this.flattenSnapshot(t),...e},s=Object.prototype.hasOwnProperty.call(e,"accessCode")?i.accessCode??void 0:t.accessCode,n=Object.prototype.hasOwnProperty.call(e,"target")||"waitlist"===i.consumer?.mechanism?i.target??void 0:void 0,o=S({base:{revision:t.revision,updatedAt:t.updatedAt,experienceId:t.experienceId,accessCode:s,experience:i.experience,events:i.events??t.events,lastSeenEventId:i.lastSeenEventId??t.lastSeenEventId},journeyStage:i.journeyStage??t.journeyStage,gates:i.gates,selected:i.selected,sequenceId:i.sequenceId,distribution:i.distribution,target:n,offers:i.offers,sequence:i.sequence,consumer:i.consumer,grant:i.grant,outcome:i.outcome});this.setSnapshot(o)}flattenSnapshot(e){return{journeyStage:e.journeyStage,gates:T(e),selected:"gated"===e.journeyStage?e.selected:void 0,sequenceId:f(e),distribution:w(e),target:x(e),offers:D(e),sequence:"routed"===e.journeyStage?e.sequence:void 0,consumer:v(e),grant:E(e),outcome:b(e),accessCode:e.accessCode,experience:e.experience,events:e.events,lastSeenEventId:e.lastSeenEventId}}emitEvent(e,t,i,s,n){const r=this.stateAtom.get(),a={id:o(),ts:Date.now(),kind:e,severity:t,audience:i,message:s,detail:n},d=[...r.events,a],c=d.length>this.EVENT_BUFFER_LIMIT?d.slice(d.length-this.EVENT_BUFFER_LIMIT):d;this.mutate({events:c})}emitIfDistributionChanged(e,t){e?.id===t?.id&&e?.lifecycle===t?.lifecycle||this.emitEvent("distribution_change","info","user","Distribution context changed",{previous:e,next:t})}buildDistributionSummary(e,t){return e.active?{type:e.active.type,id:e.active.id,sequenceId:t,lifecycle:"open",details:e.active.details,opensAt:e.active.details.openAt,closesAt:e.active.details.closeAt,updatedAt:Date.now()}:e.upcoming?{type:e.upcoming.type,id:e.upcoming.id,sequenceId:t,lifecycle:"scheduled",startsAt:e.upcoming.startsAt,opensAt:e.upcoming.startsAt,waitlistId:e.upcoming.waitlistId,updatedAt:Date.now()}:e.recentPast?{type:e.recentPast.type,id:e.recentPast.id,sequenceId:t,lifecycle:"closed",closesAt:e.recentPast.endedAt,updatedAt:Date.now()}:void 0}normalizeOffers(e){return Array.isArray(e)?e.filter(Boolean):[]}restoreFromStore(){const e=this.store.activeJourneys;if(!e||!e[this.experienceId])return null;const t=e[this.experienceId];return z(t)?t:(this.store.removeJourney(this.experienceId),null)}persist(){this.store.setJourneyState(this.experienceId,this.stateAtom.get())}setSnapshot(e,t={}){if(this.destroyed)return;const i=this.stateAtom.get();this.stateAtom.set(e),!1!==t.persist&&this.persist(),this.emitPhaseChanged(i,e)}emitPhaseChanged(e,t){const i=e.journeyStage,s="routed"===i?g(e):void 0,n=t.journeyStage,o="routed"===n?g(t):void 0;i===n&&s===o||u().emit("journey:phase-changed",{experienceId:this.experienceId,journeyStage:n,sequencePhase:o})}runSerializedOperation(e){const t=this.operationQueue.then(async()=>{if(this.destroyed)throw i.notInitialized("Journey has been destroyed");return e()});return this.operationQueue=t.then(()=>{},()=>{}),t}transitionToParticipating(e,t){const i=A({distribution:t,consumer:e});this.mutate({sequence:i,consumer:e,distribution:t})}transitionToGranted(e,t){if(this.destroyed)return;const i=this.stateAtom.get(),s=w(i),n=f(i);if(!s||!n)return void this.transitionToEnded({type:"closed",reason:"missing_distribution"});if(void 0!==t&&Date.now()>=t)return void this.transitionToEnded({type:"expired"});const o=k({token:e,distribution:s,sequenceId:n,experienceId:this.experienceId,expiresAt:t}),r=this.withGrantedConsumer(v(i),s),a=A({distribution:s,consumer:r,grant:o});this.mutate({sequence:a,consumer:r,grant:o,outcome:void 0}),this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),void 0!==t&&(this.grantExpiryTimer=setTimeout(()=>this.transitionToEnded({type:"expired"}),t-Date.now())),this.emitEvent("sequence_change","success","user","Grant issued",{grant:e}),u().emit("journey:granted",{experienceId:this.experienceId,sequenceId:n,grant:e})}transitionToEnded(e){if(this.destroyed)return;const t=this.stateAtom.get(),i=w(t),s={...e,distributionType:e.distributionType??i?.type,distributionId:e.distributionId??i?.id,at:e.at??/* @__PURE__ */(new Date).toISOString()},n=this.withTerminalConsumer(v(t),s),o=A({distribution:i,consumer:n,outcome:s});this.mutate({sequence:o,outcome:s,grant:void 0,consumer:n}),this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.emitEvent("sequence_change","expired"===s.type?"warning":"info","user","Sequence ended",{outcome:s}),u().emit("journey:ended",{experienceId:this.experienceId,sequenceId:f(this.stateAtom.get()),outcome:s})}applyMonitorUpdate(e){this.destroyed||("granted"!==e.type?this.transitionToEnded(e.outcome):this.transitionToGranted(e.token,e.expiresAt))}startMonitorForParticipation(e){if("waitlist"===e.type)return;const t=this.stateAtom.get(),i=w(t),s={displayAtom:this.ensureDisplayAtom(e)};"draw"===e.type&&"draw"===i?.details?.type&&i.details.drawTime&&(s.drawTime=i.details.drawTime);const n=this.getDistributionModule(e.type);n.startMonitoring(e.id,s,e=>this.applyMonitorUpdate(e)),this.activeMonitor={type:e.type,id:e.id,stop:()=>n.stopMonitoring(e.id)}}getDistributionModule(e){switch(e){case"queue":return this.sdk.queues;case"draw":return this.sdk.draws;case"auction":return this.sdk.auctions;case"timed_release":return this.sdk.timedReleases;case"appointment":return this.sdk.appointments;default:throw new Error(`Unknown distribution type: ${e}`)}}stopRuntimeMonitoring(e=!1){this.activeMonitor&&(this.activeMonitor.stop(),this.activeMonitor=null),e&&this.clearRuntimeDisplayState()}clearGrantExpiryTimer(){this.grantExpiryTimer&&(clearTimeout(this.grantExpiryTimer),this.grantExpiryTimer=void 0)}rearmTimersIfNeeded(){if(this.destroyed)return;const e=this.stateAtom.get();if("granted"!==g(e)||this.grantExpiryTimer)return;const t=C(e);if(!t)return;const i=t-Date.now();i<=0?this.transitionToEnded({type:"expired"}):this.grantExpiryTimer=setTimeout(()=>this.transitionToEnded({type:"expired"}),i)}getRuntimeBinding(){const e=this.stateAtom.get(),t=g(e),i=y(e),s=f(e);return"participating"===t&&i&&s&&"waitlist"!==i.type?{experienceId:this.experienceId,sequenceId:s,participationId:i.id,participationType:i.type}:null}startRuntimeMonitoring(){const e=this.getRuntimeBinding();if(!e)return;const t=this.activeMonitor;t&&t.id===e.participationId&&t.type===e.participationType&&this.displayAtom||(this.stopRuntimeMonitoring(!1),this.startMonitorForParticipation({id:e.participationId,type:e.participationType}))}getRuntimeDisplayAtom(){return this.displayAtom}applyRuntimeState(e){if(!e)return void this.clearRuntimeDisplayState();const t=this.getRuntimeBinding();if(!t)return;this.ensureDisplayAtom({id:t.participationId,type:t.participationType}).set(e)}reconcileSyncedSnapshot(e){if(this.destroyed)return;const t=this.getRuntimeBinding();if(!e)return this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),void this.setSnapshot(l(this.experienceId),{persist:!1});if(!z(e))return;const i=this.stateAtom.get();if(i.revision===e.revision&&i.updatedAt===e.updatedAt)return;const s=y(e),n=g(e),o="participating"===n&&s&&"waitlist"!==s.type?{sequenceId:f(e),participationId:s.id,participationType:s.type}:null;o?t&&t.sequenceId===o.sequenceId&&t.participationId===o.participationId&&t.participationType===o.participationType||this.resetDisplayAtom(o.participationType):this.displayAtom=null,this.setSnapshot(e,{persist:!1}),"participating"===n&&s?this.ensureDisplayAtom(s):this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.rearmTimersIfNeeded()}ensureDisplayAtom(e){return this.resetDisplayAtom(e.type),this.displayAtom}resetDisplayAtom(t){this.displayAtom&&this.displayAtom.get().type===t?this.displayAtom.set({type:t}):this.displayAtom=e({type:t})}clearRuntimeDisplayState(){const e=this.getRuntimeBinding();e?this.resetDisplayAtom(e.participationType):this.displayAtom=null}enteredConsumer(e,t){switch(e){case"queue":return{mechanism:"queue",status:"queued",id:t,distributionId:t};case"draw":return{mechanism:"draw",status:"entered",id:t,distributionId:t};case"auction":return{mechanism:"auction",status:"bidding",id:t,distributionId:t};case"timed_release":return{mechanism:"timed_release",status:"entered",id:t,distributionId:t}}}mapEnterResultToUpdate(e,t){if("auction"===e||void 0===t)return null;if("queue"===e){const e=t;return"QUEUED"===e.status?null:"GRANTED"===e.status||"COMPLETED"===e.status?{type:"granted",token:e.admissionGrant,expiresAt:"expiresAt"in e&&e.expiresAt?new Date(e.expiresAt).getTime():void 0}:{type:"ended",outcome:{type:this.queueOutcome(e),reason:"reason"in e?e.reason:void 0}}}if("draw"===e){const e=t;return"ENTERED"===e.status?null:"WON"===e.status||"COMPLETED"===e.status?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:{type:"ended",outcome:{type:"DENIED"===e.status?"denied":"lost",reason:"reason"in e?e.reason:void 0}}}const i=t;return"ENTERED"===i.status?null:"COMPLETED"===i.status?{type:"ended",outcome:{type:"completed"}}:{type:"ended",outcome:{type:"LEFT"===i.status?"left":"denied",reason:"reason"in i?i.reason:void 0}}}queueOutcome(e){return"LEFT"===e.status?"left":"EXPIRED"===e.status?"expired":"COMPLETED"===e.status?"completed":"DENIED"===e.status?"denied":"closed"}deriveTerminalOutcomeForClosedDistribution(e,t,i){if(i)return i;if(t&&"closed"===t.lifecycle&&e)switch(e.mechanism){case"draw":case"auction":return{type:"lost",distributionType:t.type,distributionId:t.id};case"waitlist":return{type:"expired",distributionType:t.type,distributionId:t.id};default:return{type:"closed",distributionType:t.type,distributionId:t.id}}}withGrantedConsumer(e,t){switch(e?.mechanism){case"queue":return{...e,status:"granted"};case"draw":case"auction":return{...e,status:"won"};case"timed_release":return{...e,status:"completed"};case void 0:if("queue"===t.type)return{mechanism:"queue",status:"granted",id:t.id,distributionId:t.id};if("draw"===t.type)return{mechanism:"draw",status:"won",id:t.id,distributionId:t.id};if("auction"===t.type)return{mechanism:"auction",status:"won",id:t.id,distributionId:t.id};if("timed_release"===t.type)return{mechanism:"timed_release",status:"completed",id:t.id,distributionId:t.id};throw i.validationError("Distribution does not produce grants");default:if(!e)throw i.validationError("Grant requires a consumer state");return e}}withTerminalConsumer(e,t){if(e){if(!t)return e;switch(e.mechanism){case"queue":return"left"===t.type?{...e,status:"left"}:"denied"===t.type?{...e,status:"denied",deniedReason:t.reason}:"completed"===t.type?{...e,status:"completed"}:{...e,status:"not_queued"};case"draw":return"denied"===t.type?{...e,status:"denied",deniedReason:t.reason}:"completed"===t.type?{...e,status:"completed"}:"won"===t.type?{...e,status:"won"}:{...e,status:"not_entered"};case"auction":return"won"===t.type?{...e,status:"won"}:{...e,status:"lost"};case"timed_release":return"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"};case"appointment":return"cancelled"===t.type?{...e,status:"cancelled"}:"no_show"===t.type?{...e,status:"no_show"}:"completed"===t.type?{...e,status:"completed"}:{...e,status:"not_booked"};case"waitlist":return"left"===t.type?{...e,status:"left"}:{...e,status:"expired"}}}}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=void 0)}}const R=["revision","updatedAt","experienceId","accessCode","experience","events","lastSeenEventId","journeyStage"],M={ready:new Set(R),routing:new Set(R),gated:/* @__PURE__ */new Set([...R,"gates","selected"]),routed:/* @__PURE__ */new Set([...R,"sequenceId","offers","sequence"])},_=/* @__PURE__ */new Set(["sequence","status"]),O=/* @__PURE__ */new Set(["phase","reason","mechanism","distribution","target","consumer","grant","outcome"]),N=/* @__PURE__ */new Set(["type","id","sequenceId","lifecycle","startsAt","opensAt","closesAt","settlesAt","waitlistId","details","updatedAt"]),B=/* @__PURE__ */new Set(["status","id","distributionId","waitlistId","enteredAt","admissionGrant","grantedAt","deniedReason","position","highestBid","currentBid","slotId","locationId","bookedAt","checkedInAt","completedAt","cancelledAt","noShowAt","notifiedAt"]),P=/* @__PURE__ */new Set(["token","distributionType","distributionId","sequenceId","experienceId","issuedAt","expiresAt","handoffUrl"]),G=/* @__PURE__ */new Set(["type","reason","at","distributionType","distributionId"]);function U(e,t){return!!e&&"object"==typeof e&&!Array.isArray(e)&&Object.keys(e).every(e=>t.has(e))}function z(e){if(!e||"object"!=typeof e)return!1;const t=e,i=t.journeyStage;if("ready"!==i&&"routing"!==i&&"gated"!==i&&"routed"!==i)return!1;if(!U(t,M[i]))return!1;if(void 0!==t.selected&&!U(t.selected,_))return!1;if("routed"!==i)return!0;const s=t.sequence;if(!function(e){return!(!U(e,O)||"distribution"in e&&!U(e.distribution,N)||"target"in e&&!U(e.target,N)||"consumer"in e&&!U(e.consumer,B)||"grant"in e&&!U(e.grant,P)||"outcome"in e&&!U(e.outcome,G))}(s))return!1;const n=s.phase;return"unavailable"===n||"scheduled"===n||"enterable"===n||"participating"===n||"settling"===n||"granted"===n||"ended"===n}export{j as ExperienceJourney};
@@ -0,0 +1,79 @@
1
+ import { AdmissionGrant, DistributionSummary, GatedSnapshot, JourneySnapshot, JourneyStage, MechanismConsumerState, Participation, ReadySnapshot, RoutedSnapshot, RoutingGate, RoutingOffer, SequenceMechanism, SequenceOutcome, SequenceState, SequenceTargetState } from './journey.types';
2
+ import { ExperienceSession, Sequence } from './types';
3
+ export declare class SnapshotInvariantError extends Error {
4
+ constructor(phase: string, missing: string);
5
+ }
6
+ export declare function isGateState(state: JourneySnapshot): state is GatedSnapshot;
7
+ export declare function isRoutedState(state: JourneySnapshot): state is RoutedSnapshot;
8
+ export declare function getGates(state: JourneySnapshot): RoutingGate[];
9
+ export declare function getSequenceId(state: JourneySnapshot): string | undefined;
10
+ export declare function getDistribution(state: JourneySnapshot): DistributionSummary | undefined;
11
+ export declare function getTarget(state: JourneySnapshot): SequenceTargetState | undefined;
12
+ export declare function getConsumer(state: JourneySnapshot): MechanismConsumerState | undefined;
13
+ export declare function getParticipation(state: JourneySnapshot): Participation | undefined;
14
+ export declare function getAdmissionGrant(state: JourneySnapshot): string | undefined;
15
+ export declare function getGrant(state: JourneySnapshot): AdmissionGrant | undefined;
16
+ export declare function getAdmissionGrantExpiresAt(state: JourneySnapshot): number | undefined;
17
+ export declare function getOutcome(state: JourneySnapshot): SequenceOutcome | undefined;
18
+ export declare function getSelected(state: JourneySnapshot): GatedSnapshot["selected"] | undefined;
19
+ export declare function getOffers(state: JourneySnapshot): RoutingOffer[];
20
+ export declare function getSequencePhase(state: JourneySnapshot): SequenceState["phase"];
21
+ export declare function getSequenceState(state: JourneySnapshot): SequenceState | undefined;
22
+ export declare function createInitialSnapshot(experienceId: string): ReadySnapshot;
23
+ interface SnapshotBase {
24
+ revision: number;
25
+ updatedAt: number;
26
+ experienceId: string;
27
+ accessCode?: string;
28
+ experience?: ExperienceSession;
29
+ events: JourneySnapshot["events"];
30
+ lastSeenEventId?: string;
31
+ }
32
+ export declare function extractBase(state: JourneySnapshot): SnapshotBase;
33
+ export declare function buildSnapshot(params: {
34
+ base: SnapshotBase;
35
+ journeyStage: JourneyStage;
36
+ gates?: RoutingGate[];
37
+ selected?: {
38
+ sequence: Sequence;
39
+ status: "gated";
40
+ };
41
+ sequenceId?: string;
42
+ offers?: RoutingOffer[];
43
+ sequence?: SequenceState;
44
+ distribution?: DistributionSummary;
45
+ target?: SequenceTargetState;
46
+ consumer?: MechanismConsumerState;
47
+ grant?: AdmissionGrant;
48
+ outcome?: SequenceOutcome;
49
+ }): JourneySnapshot;
50
+ export declare function validateSequenceState(data: SequenceState): SequenceState;
51
+ export declare function createGrant(params: {
52
+ token: string;
53
+ distribution: DistributionSummary;
54
+ sequenceId: string;
55
+ experienceId: string;
56
+ expiresAt?: number;
57
+ }): AdmissionGrant;
58
+ export declare function deriveSequenceState(params: {
59
+ distribution?: DistributionSummary;
60
+ target?: SequenceTargetState;
61
+ consumer?: MechanismConsumerState;
62
+ grant?: AdmissionGrant;
63
+ outcome?: SequenceOutcome;
64
+ }): SequenceState;
65
+ export declare function normalizeSequenceState(params: {
66
+ sequence: SequenceState;
67
+ distribution?: DistributionSummary;
68
+ target?: SequenceTargetState;
69
+ consumer?: MechanismConsumerState;
70
+ grant?: AdmissionGrant;
71
+ outcome?: SequenceOutcome;
72
+ }): SequenceState;
73
+ export declare function sequenceDistribution(sequence: SequenceState): DistributionSummary | undefined;
74
+ export declare function sequenceTarget(sequence: SequenceState): SequenceTargetState | undefined;
75
+ export declare function withMechanism(mechanism: SequenceMechanism, consumer: unknown): MechanismConsumerState;
76
+ export declare function stripMechanism<T extends {
77
+ mechanism: SequenceMechanism;
78
+ }>(consumer: T): Omit<T, "mechanism">;
79
+ export {};
@@ -0,0 +1 @@
1
+ class e extends Error{constructor(e,t){super(`SnapshotInvariantError: phase '${e}' requires ${t}`),this.name="SnapshotInvariantError"}}function t(e){return"gated"===e.journeyStage}function n(e){return"routed"===e.journeyStage}function i(e){return t(e)?e.gates:[]}function r(e){return n(e)?e.sequenceId:void 0}function s(e){if(!n(e))return;const t=e.sequence;return"distribution"in t?t.distribution:void 0}function o(e){if(!n(e))return;const t=e.sequence;return"target"in t?t.target:void 0}function u(e){if(!n(e))return;const t=e.sequence;return"mechanism"in t?S(t.mechanism,t.consumer):void 0}function a(e){if(!n(e))return;if("participating"!==e.sequence.phase)return;const t=e.sequence,i=t.consumer,r="waitlist"===t.mechanism?D(i.waitlistId)??D(i.id)??o(e)?.waitlistId:D(i.distributionId)??D(i.id)??s(e)?.id;return r?{id:r,type:t.mechanism}:void 0}function c(e){return d(e)?.token}function d(e){return n(e)&&"granted"===e.sequence.phase?e.sequence.grant:void 0}function m(e){const t=d(e);if(t?.expiresAt)return new Date(t.expiresAt).getTime()}function h(e){return n(e)&&"ended"===e.sequence.phase?e.sequence.outcome:void 0}function p(e){return t(e)?e.selected:void 0}function l(e){return n(e)?e.offers:[]}function w(e){return n(e)?e.sequence.phase:"unavailable"}function f(e){return n(e)?e.sequence:void 0}function g(e){return{revision:1,updatedAt:Date.now(),journeyStage:"ready",experienceId:e,events:[],lastSeenEventId:void 0}}function b(t){const n=function(e){return{...e,revision:e.revision+1,updatedAt:Date.now()}}(t.base);switch(t.journeyStage){case"ready":return{...n,journeyStage:"ready"};case"routing":return{...n,journeyStage:"routing"};case"gated":return{...n,journeyStage:"gated",gates:t.gates??[],selected:t.selected};case"routed":{if(!t.sequenceId)throw new e("routed","sequenceId");const i=t.sequence??q({distribution:t.distribution,target:t.target,consumer:t.consumer,grant:t.grant,outcome:t.outcome});return y(i),{...n,journeyStage:"routed",sequenceId:t.sequenceId,offers:t.offers??[],sequence:i}}}throw new e(String(t.journeyStage),"known journeyStage")}function y(t){const n=t;switch(t.phase){case"unavailable":default:return t;case"scheduled":if(!("mechanism"in n))throw new e("scheduled","mechanism");if("waitlist"===n.mechanism)throw new e("scheduled","primary mechanism");if(!("distribution"in n))throw new e("scheduled","distribution");if(!("consumer"in n))throw new e("scheduled","consumer");return t;case"enterable":case"participating":if(!("mechanism"in n))throw new e(t.phase,"mechanism");if("waitlist"===n.mechanism){if(!("target"in n))throw new e(t.phase,"target")}else if(!("distribution"in n))throw new e(t.phase,"distribution");if(!("consumer"in n))throw new e(t.phase,"consumer");return t;case"settling":if(!("mechanism"in n))throw new e("settling","mechanism");if("draw"!==t.mechanism&&"auction"!==t.mechanism)throw new e("settling","draw or auction mechanism");if(!("distribution"in n))throw new e("settling","distribution");if(!("consumer"in n))throw new e("settling","consumer");return t;case"granted":if(!("mechanism"in n))throw new e("granted","mechanism");if("appointment"===n.mechanism||"waitlist"===n.mechanism)throw new e("granted","grant-producing mechanism");if(!("distribution"in n))throw new e("granted","distribution");if(!("consumer"in n))throw new e("granted","consumer");if(!("grant"in n))throw new e("granted","grant");return t;case"ended":if(!("mechanism"in n))throw new e("ended","mechanism");if("waitlist"===n.mechanism){if(!("target"in n))throw new e("ended","target")}else if(!("distribution"in n))throw new e("ended","distribution");if(!("consumer"in n))throw new e("ended","consumer");if(!t.outcome)throw new e("ended","outcome");return t}}function I(t){if("appointment"===t.distribution.type)throw new e("granted","grant-producing mechanism");return{token:t.token,distributionType:t.distribution.type,distributionId:t.distribution.id,sequenceId:t.sequenceId,experienceId:t.experienceId,expiresAt:t.expiresAt?new Date(t.expiresAt).toISOString():void 0}}function q(e){const{distribution:t,target:n,consumer:i,grant:r,outcome:s}=e;if("waitlist"===i?.mechanism||n){const e=n??A(t);if(!e)return{phase:"unavailable",reason:"missing_waitlist"};const r="waitlist"===i?.mechanism?x(i):{status:"not_waitlisted"},o=s??k("waitlist",r);return o?{phase:"ended",mechanism:"waitlist",target:e,consumer:r,outcome:o}:{phase:"not_waitlisted"===r.status?"enterable":"participating",mechanism:"waitlist",target:e,consumer:r}}if(!t)return{phase:"unavailable"};const o=t.type,u=i&&i.mechanism===o?x(i):j(t);if(r)return r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===t.lifecycle?{phase:"enterable",mechanism:o,distribution:t,consumer:j(t)}:_(o,t,u,{type:"expired"}):{phase:"granted",mechanism:o,distribution:t,consumer:u,grant:r};const a=s??k(o,u);if(a)return _(o,t,u,a);if(function(e,t){const n=t.status;switch(e){case"queue":return"queued"===n;case"draw":case"timed_release":return"entered"===n;case"auction":return"bidding"===n||"winning"===n||"outbid"===n;case"appointment":return"booked"===n||"checked_in"===n}}(o,u))return{phase:"participating",mechanism:o,distribution:t,consumer:u};if("scheduled"===t.lifecycle&&t.waitlistId){const e=A(t);if(e)return{phase:"enterable",mechanism:"waitlist",target:e,consumer:{status:"not_waitlisted"}}}return"scheduled"===t.lifecycle?{phase:"scheduled",mechanism:o,distribution:t,consumer:u}:"open"===t.lifecycle?{phase:"enterable",mechanism:o,distribution:t,consumer:u}:"settling"!==t.lifecycle||"draw"!==o&&"auction"!==o?_(o,t,u,{type:"closed"}):{phase:"settling",mechanism:o,distribution:t,consumer:u}}function v(e){try{return y(e.sequence)}catch{return q({distribution:e.distribution,target:e.target,consumer:e.consumer,grant:e.grant,outcome:e.outcome})}}function S(e,t){return{...t,mechanism:e}}function x(e){const{mechanism:t,...n}=e;return n}function _(e,t,n,i){return{phase:"ended",mechanism:e,distribution:t,consumer:n,outcome:{...i,distributionType:i.distributionType??t.type,distributionId:i.distributionId??t.id,at:i.at??/* @__PURE__ */(new Date).toISOString()}}}function A(e){if(e?.waitlistId)return{type:e.type,id:e.id,sequenceId:e.sequenceId,lifecycle:e.lifecycle,opensAt:e.opensAt,closesAt:e.closesAt,waitlistId:e.waitlistId}}function j(e){switch(e.type){case"queue":return{status:"not_queued",distributionId:e.id};case"draw":case"timed_release":return{status:"not_entered",distributionId:e.id};case"auction":return{status:"not_bid",distributionId:e.id};case"appointment":return{status:"not_booked",distributionId:e.id}}}function k(e,t){const n=t.status,i=t.deniedReason;switch(e){case"queue":case"timed_release":return"completed"===n?{type:"completed"}:"denied"===n?{type:"denied",reason:i}:"left"===n?{type:"left"}:void 0;case"draw":return"completed"===n?{type:"completed"}:"denied"===n?{type:"denied",reason:i}:void 0;case"auction":return"lost"===n?{type:"lost"}:void 0;case"appointment":return"completed"===n?{type:"completed"}:"cancelled"===n?{type:"cancelled"}:"no_show"===n?{type:"no_show"}:void 0;case"waitlist":return"expired"===n?{type:"expired"}:"left"===n?{type:"left"}:void 0}}function D(e){return"string"==typeof e&&e.length>0?e:void 0}export{e as SnapshotInvariantError,b as buildSnapshot,I as createGrant,g as createInitialSnapshot,q as deriveSequenceState,c as getAdmissionGrant,m as getAdmissionGrantExpiresAt,u as getConsumer,s as getDistribution,i as getGates,d as getGrant,l as getOffers,h as getOutcome,a as getParticipation,p as getSelected,r as getSequenceId,w as getSequencePhase,f as getSequenceState,o as getTarget,t as isGateState,n as isRoutedState,v as normalizeSequenceState,x as stripMechanism,y as validateSequenceState,S as withMechanism};