engine

package
v0.0.0-...-3d30ee6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package engine orchestrates core transitions over the store within transactions: recording, reads, timezone changes, repair, and the outbox.

Index

Constants

This section is empty.

Variables

View Source
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

func MigrateDB

func MigrateDB(ctx context.Context, db *sql.DB) error

MigrateDB applies migrations on a caller-provided handle (for hosts that already manage a database/sql connection).

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 New

func New(pool *pgxpool.Pool, opts ...Option) (*Engine, error)

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

func (e *Engine) Get(ctx context.Context, subject, key string) (StreakView, error)

Get returns the derived view of one streak. It never writes: correctness does not depend on any settler having run.

func (*Engine) List

func (e *Engine) List(ctx context.Context, subject string) ([]StreakView, error)

List returns derived views of all streaks of a subject.

func (*Engine) Migrate

func (e *Engine) Migrate(ctx context.Context) error

Migrate applies the streaks schema migrations through a temporary database/sql handle on the same connection string.

func (*Engine) PollEvents

func (e *Engine) PollEvents(ctx context.Context, after int64, limit int) ([]Event, error)

PollEvents returns outbox events with id > after, oldest first.

func (*Engine) Record

func (e *Engine) Record(ctx context.Context, req RecordReq) (StreakView, error)

Record reports activity and returns the derived post-activity view.

func (*Engine) Recount

func (e *Engine) Recount(ctx context.Context, subject, key string) (StreakView, error)

Recount rebuilds state from the ledger (the oracle made operational).

func (*Engine) Repair

func (e *Engine) Repair(ctx context.Context, subject, key string) (StreakView, error)

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

func (e *Engine) RunScheduler(ctx context.Context, interval time.Duration) error

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

func (e *Engine) SetFreezes(ctx context.Context, subject, key string, n int) (StreakView, error)

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

func (e *Engine) SetReminder(ctx context.Context, subject, key, localTime string) error

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

func (e *Engine) SetTimezone(ctx context.Context, subject, tz string) error

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

func (e *Engine) Tick(ctx context.Context) error

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).

func (*Engine) Timezone

func (e *Engine) Timezone(ctx context.Context, subject string) (string, error)

Timezone returns the subject's current zone (default if never set).

func (*Engine) Unrecord

func (e *Engine) Unrecord(ctx context.Context, subject, key string) (StreakView, error)

Unrecord removes the current period's activity (toggle semantics) and rebuilds state from the ledger so longest/freeze accounting stays exact.

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 FreezesView struct {
	Available int `json:"available"`
	Max       int `json:"max"`
	// Progress / Needed track earning the next freeze (0/0 when disabled).
	Progress int `json:"progress"`
	Needed   int `json:"needed"`
}

type MilestoneView

type MilestoneView struct {
	Reached int `json:"reached"`
	Next    int `json:"next,omitempty"`
}

type Option

type Option func(*Engine)

func WithClock

func WithClock(now func() time.Time) Option

WithClock injects a time source (tests, simulations).

func WithDefaultTimezone

func WithDefaultTimezone(tz string) Option

WithDefaultTimezone sets the timezone for subjects that never called SetTimezone. Defaults to UTC.

func WithEventHandler

func WithEventHandler(h func(Event)) Option

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

func WithLogger(l *slog.Logger) Option

WithLogger overrides the scheduler's logger (defaults to slog.Default()).

func WithStreakType

func WithStreakType(key string, cfg core.Config) Option

WithStreakType registers a config template; Record auto-creates streaks for registered keys on first activity.

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.

type TargetView

type TargetView struct {
	Goal int `json:"goal"`
	Done int `json:"done"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL