Documentation
¶
Overview ¶
Package engine orchestrates core transitions over the store within transactions: recording, reads, timezone changes, repair, and the outbox.
Index ¶
- Variables
- func MigrateDB(ctx context.Context, db *sql.DB) error
- type CalendarDay
- type Engine
- func (e *Engine) Calendar(ctx context.Context, subject, key string, from, to core.Date) ([]CalendarDay, error)
- func (e *Engine) Get(ctx context.Context, subject, key string) (StreakView, error)
- func (e *Engine) List(ctx context.Context, subject string) ([]StreakView, error)
- func (e *Engine) Migrate(ctx context.Context) error
- func (e *Engine) PollEvents(ctx context.Context, after int64, limit int) ([]Event, error)
- func (e *Engine) Record(ctx context.Context, req RecordReq) (StreakView, error)
- func (e *Engine) Recount(ctx context.Context, subject, key string) (StreakView, error)
- func (e *Engine) Repair(ctx context.Context, subject, key string) (StreakView, error)
- func (e *Engine) ReplaceHistory(ctx context.Context, subject, key string, periods []core.Date) (StreakView, error)
- func (e *Engine) RunScheduler(ctx context.Context, interval time.Duration) error
- func (e *Engine) SetFreezes(ctx context.Context, subject, key string, n int) (StreakView, error)
- func (e *Engine) SetReminder(ctx context.Context, subject, key, localTime string) error
- func (e *Engine) SetTimezone(ctx context.Context, subject, tz string) error
- func (e *Engine) ShiftHistory(ctx context.Context, subject, key string, days int) (StreakView, error)
- func (e *Engine) Tick(ctx context.Context) error
- func (e *Engine) Timezone(ctx context.Context, subject string) (string, error)
- func (e *Engine) Unrecord(ctx context.Context, subject, key string) (StreakView, error)
- type Event
- type FreezesView
- type MilestoneView
- type Option
- type RecordReq
- type StreakView
- type TargetView
Constants ¶
This section is empty.
Variables ¶
var ( ErrUnknownStreakType = errors.New("streakd: unregistered streak key and no config provided") ErrNotFound = errors.New("streakd: not found") ErrBadTimezone = errors.New("streakd: invalid IANA timezone") ErrOutsidePeriod = errors.New("streakd: activity outside the current period") ErrNothingToRepair = errors.New("streakd: no recent break to repair") )
Functions ¶
Types ¶
type CalendarDay ¶
type CalendarDay struct {
Period string `json:"period"`
Amount int `json:"amount"`
Earned bool `json:"earned"`
}
CalendarDay is one rendered cell for history UIs.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the embedded streakd instance. Safe for concurrent use.
func (*Engine) Calendar ¶
func (e *Engine) Calendar(ctx context.Context, subject, key string, from, to core.Date) ([]CalendarDay, error)
Calendar returns the mark history between two dates inclusive.
func (*Engine) Get ¶
Get returns the derived view of one streak. It never writes: correctness does not depend on any settler having run.
func (*Engine) Migrate ¶
Migrate applies the streaks schema migrations through a temporary database/sql handle on the same connection string.
func (*Engine) PollEvents ¶
PollEvents returns outbox events with id > after, oldest first.
func (*Engine) Repair ¶
Repair restores a streak to its pre-break length. Allowed while the most recent break is younger than the repair window; any periods earned since the break are kept on top of the restored count.
func (*Engine) ReplaceHistory ¶
func (e *Engine) ReplaceHistory(ctx context.Context, subject, key string, periods []core.Date) (StreakView, error)
ReplaceHistory atomically replaces a streak's entire ledger with the given earned periods (ascending or not — they are deduplicated by the earn-once primary key) and recomputes state by replay. An empty list resets the streak to a blank slate. Uses: importing history from a legacy system, support corrections, and test setup.
func (*Engine) RunScheduler ¶
RunScheduler ticks until ctx is cancelled. The scheduler exists only for side effects — near-real-time settlement events and at-risk reminders. Correctness of every read never depends on it running.
func (*Engine) SetFreezes ¶
SetFreezes sets the freeze inventory directly (support grants, test setup). The value may exceed the config cap; the cap only limits earning.
func (*Engine) SetReminder ¶
SetReminder sets (or clears, with empty string) the local-time at-risk reminder for one streak, e.g. "20:30". The subject and streak are created if missing (registered streak types only), so a reminder chosen before the first activity is not lost.
func (*Engine) SetTimezone ¶
SetTimezone moves a subject to a new IANA zone, applying the generosity rule to every streak: first all elapsed periods are settled under the OLD zone (real misses cost what they cost), then any gap created purely by the zone shift itself is forgiven. A timezone change can therefore never break a streak; at most it delays the next earnable period.
func (*Engine) ShiftHistory ¶
func (e *Engine) ShiftHistory(ctx context.Context, subject, key string, days int) (StreakView, error)
ShiftHistory moves the whole ledger `days` days into the past (positive = older), preserving the freeze inventory and the settle pointer's relative position. State is NOT recounted: the next read derives the aged state and the next Record/scheduler tick settles it with real events — exactly what a test wants to observe. Day-period streaks only.
func (*Engine) Tick ¶
Tick runs one scheduler pass: settle everything due, then emit due at-risk reminders. Safe to run from multiple processes (advisory lock) and idempotent within a period (settle pointers, reminder claims).
type Event ¶
type Event struct {
ID int64 `json:"id"`
Subject string `json:"subject"`
Key string `json:"key"`
Type core.EventType `json:"type"`
Period string `json:"period"`
Count int `json:"count"`
CreatedAt time.Time `json:"created_at"`
}
Event is the engine-level event delivered to handlers and pollers.
type FreezesView ¶
type MilestoneView ¶
type Option ¶
type Option func(*Engine)
func WithDefaultTimezone ¶
WithDefaultTimezone sets the timezone for subjects that never called SetTimezone. Defaults to UTC.
func WithEventHandler ¶
WithEventHandler registers a synchronous post-commit callback. Delivery is at-least-once from this process's successful transactions; cross-process consumers should poll the outbox instead.
func WithLogger ¶
WithLogger overrides the scheduler's logger (defaults to slog.Default()).
type RecordReq ¶
type RecordReq struct {
Subject string
Key string
// At defaults to the engine clock. When set, it must land in the current
// period (a short grace window covers boundary races for the previous,
// not-yet-settled period).
At time.Time
// Amount defaults to 1; periods earn when the accumulated amount reaches
// the config threshold.
Amount int
// IdempotencyKey memoizes the response: replays return the original
// result without re-recording.
IdempotencyKey string
// Config creates the streak with this config on first activity instead of
// a registered streak type.
Config *core.Config
}
RecordReq describes one activity report.
type StreakView ¶
type StreakView struct {
Key string `json:"key"`
Count int `json:"count"`
Longest int `json:"longest"`
State core.Liveness `json:"state"`
EarnedThisPeriod bool `json:"earned_this_period"`
CurrentPeriod string `json:"current_period"`
AmountThisPeriod int `json:"amount_this_period"`
AmountNeeded int `json:"amount_needed"`
LossAt *time.Time `json:"loss_at,omitempty"`
SecondsUntilLoss int64 `json:"seconds_until_loss"`
Freezes FreezesView `json:"freezes"`
Milestone *MilestoneView `json:"milestone,omitempty"`
Target *TargetView `json:"target,omitempty"`
Timezone string `json:"timezone"`
}
StreakView is the display-ready, always-derived state of one streak. It is JSON-stable: clients and idempotency memoization both serialize it.