package pier import ( "context" "encoding/json" "time" ) // Record is one stored session: the binding key of the identity that opened // it, the JSON of the SDK's ServerSessionState once initialised (nil until // then) and the creation time. type Record struct { Binding string `json:"binding"` State json.RawMessage `json:"state,omitempty"` Created time.Time `json:"created"` } // Event is what instances publish to each other: a session ended by DELETE // (EventEnded, with SessionID) or a subject change (the pact // MCPSubjectChanged kinds, with Guard and UserID). Instance names the // publisher so it can skip its own echo when it has already acted. type Event struct { Kind string `json:"kind"` Guard string `json:"guard,omitempty"` UserID uint `json:"user_id,omitempty"` SessionID string `json:"session_id,omitempty"` Instance string `json:"instance,omitempty"` TokenID uint `json:"token_id,omitempty"` ClientID string `json:"client_id,omitempty"` } // EventEnded is the kind of a session-ended event. const EventEnded = "ended" // Store is the session registry and the event channel shared by every // instance of the application: memory for one instance (and tests), Redis // for several. Put writes a record with a TTL, Touch slides it, Count // answers the live sessions of one binding, Publish and Subscribe carry // Events. type Store interface { Put(ctx context.Context, id string, rec Record, ttl time.Duration) error Get(ctx context.Context, id string) (Record, bool, error) Touch(ctx context.Context, id string, ttl time.Duration) error Delete(ctx context.Context, id string) error Count(ctx context.Context, binding string) (int, error) Publish(ctx context.Context, ev Event) error Subscribe(ctx context.Context, fn func(Event)) error } // updater is implemented by the stores pier ships: Update rewrites an // existing record and never creates one (Redis SET XX), answering false when // the record is gone, so a request that finishes after its session ended // (DELETE, revocation, deletion, expiry) cannot write the record back. type updater interface { Update(ctx context.Context, id string, rec Record, ttl time.Duration) (bool, error) } // reserver is implemented by the stores pier ships: Reserve is the atomic // form of Count then Put (A-WR-03). It writes rec for id with the TTL and // adds id to the binding's sessions only when the binding holds fewer than // max live sessions, answering false (nothing written) at the cap, so // concurrent initializes of one binding cannot exceed it. Release drops a // reservation whose session never opened (a failed connect or initialize). type reserver interface { Reserve(ctx context.Context, binding, id string, rec Record, max int, ttl time.Duration) (bool, error) Release(ctx context.Context, binding, id string) error }