package session import ( "context" "errors" "fmt" ) // ReadThroughCache resolves authenticated sessions from a process-local // SnapshotStore first and falls back to another Cache only on a local miss. type ReadThroughCache struct { local SnapshotStore fallback Cache } // NewReadThroughCache constructs a hot-path cache that seeds local snapshots // from fallback on demand. func NewReadThroughCache(local SnapshotStore, fallback Cache) (*ReadThroughCache, error) { if local == nil { return nil, errors.New("new read-through session cache: nil local cache") } if fallback == nil { return nil, errors.New("new read-through session cache: nil fallback cache") } return &ReadThroughCache{ local: local, fallback: fallback, }, nil } // Lookup resolves deviceSessionID from local first, then performs one fallback // lookup on a local miss and seeds the local cache with the returned snapshot. func (c *ReadThroughCache) Lookup(ctx context.Context, deviceSessionID string) (Record, error) { if c == nil { return Record{}, errors.New("lookup session from read-through cache: nil cache") } record, err := c.local.Lookup(ctx, deviceSessionID) switch { case err == nil: return record, nil case !errors.Is(err, ErrNotFound): return Record{}, fmt.Errorf("lookup session from read-through cache: %w", err) } record, err = c.fallback.Lookup(ctx, deviceSessionID) if err != nil { return Record{}, err } if err := c.local.Upsert(record); err != nil { return Record{}, fmt.Errorf("lookup session from read-through cache: seed local cache: %w", err) } return cloneRecord(record), nil } // Local returns the mutable process-local snapshot store used by c. func (c *ReadThroughCache) Local() SnapshotStore { if c == nil { return nil } return c.local } var _ Cache = (*ReadThroughCache)(nil)