Runlight's tables in a SQL database: SQLite, Postgres, or MySQL and MariaDB. The same tables, columns, and statements as the TypeScript SDK's SqlStore, so one database can be served by any implementation.
Make one over an app's Ecto repo with ecto/1:
Runlight.Store.ecto(repo: MyApp.Repo)or over any Runlight.Db with new/1.
Summary
Functions
The top values of a dimension, limit from offset.
Adds up one local day of a site: totals, each visit dimension, and pages. A visit belongs to the day it started.
Records a period as sent. Only one caller wins, so two cron runs at once cannot both send it.
Forgets built days, all of a site's (no options), those starting before
before:, or those touching from: to to:, so they are built again.
Hides a link and frees its slug; its clicks stay in the history.
Deleting a share is how it is revoked: the link stops working at once.
Deletes a site and everything recorded for it. Its events and visits go a day at a time first, and what is left goes in one transaction.
Deleting a token is how it is revoked: it stops working at once.
Deletes a site's visits and events from before a time, for its retention setting.
Deletes a site's events from from on whose visit no longer exists, a day at a time.
Deletes every salt older than day, so old hashes can never be recomputed.
A store over an app's Ecto repo; see Runlight.Db.Ecto for the options.
The property names sent with an event in a query's range, most used first.
The values one property of an event took, with how often and by how many visitors.
When Runlight itself first counted a visit, leaving out imported history.
When the site's first visit was recorded, or nil with no data yet.
How many visits reached each step, in order, within the same visit. Step one is the first matching row in the range; each later step must come after the step before it. Filters choose which visits enter the funnel.
A goal's conversions split by where the visit came from, or by the page it happened on.
A goal's conversions and revenue in each bucket, by when each visit started.
One goal's conversions, converting visitors, and revenue for a query's range and filters.
Every goal's totals in one pass over the range's events, by goal id.
Visits by quarter hour since the epoch, which the caller folds into local weekdays and hours, keeping time zones out of SQL.
Records an event; row is a map with the EventRow's fields as atoms in snake case.
Records a new session; row is a map with the SessionRow's fields as atoms in snake case.
Each visit's pageviews in order, at most per_visit of them, for
journeys. Visits belong to the range they started in.
When the site last recorded a visit, or nil if it never has.
One link's clicks by a visit dimension.
The live link with a slug. Slugs are unique across every domain.
One link's clicks per bucket.
A site's links, newest first, with their clicks in a range.
Creates the tables, and brings an older database up to date. Safe to call any number of times.
A store over a Runlight.Db.
The visitor's open session: any of their hashes, active since since.
Keeps SQLite's planner statistics current, which it never gathers by itself. A sample of each index is enough. Postgres gathers its own.
The pageview an engagement ping or event belongs to, with when its visit started and was last active.
Who is on the site now, what they read, and the latest activity.
Puts a period back when its email failed, so the next run tries again.
Removes a domain. Its links keep it as their home and fall back to the app's own link path until it is added again.
The days of a site already built.
The salt for a day, made on first ask. Two racing callers agree on one.
The headline numbers for each bucket.
Every setting whose key starts with a prefix, such as each connected install's.
Settings changed in the dashboard, by site. They win over the ones in code.
The sites kept in the database, by name.
The headline numbers for a query.
Reads a setting and deletes it. Of two callers at once, only the one whose delete took the row gets its value.
Counts a row into its session. An event with reopen false, one that joins
a visit already ended, counts without moving the session's last activity.
After a late event or engagement ping joins an old visit, the day that visit started may already be added up. Forget that day so the next check builds it again.
Runs fun with a store whose every query is in one transaction.
Just the visitor count from stats, in one query, for conversion rates.
Types
@type t() :: %Runlight.Store{db: Runlight.Db.t()}
Functions
@spec breakdown(t(), map(), String.t(), pos_integer(), non_neg_integer()) :: [ Runlight.JS.Object.t() ]
The top values of a dimension, limit from offset.
Adds up one local day of a site: totals, each visit dimension, and pages. A visit belongs to the day it started.
Records a period as sent. Only one caller wins, so two cron runs at once cannot both send it.
Forgets built days, all of a site's (no options), those starting before
before:, or those touching from: to to:, so they are built again.
Hides a link and frees its slug; its clicks stay in the history.
Deletes a site and everything recorded for it. Its events and visits go a day at a time first, and what is left goes in one transaction.
Deleting a token is how it is revoked: it stops working at once.
Deletes a site's visits and events from before a time, for its retention setting.
Deletes a site's events from from on whose visit no longer exists, a day at a time.
Deletes every salt older than day, so old hashes can never be recomputed.
A store over an app's Ecto repo; see Runlight.Db.Ecto for the options.
@spec event_prop_keys(t(), map(), String.t()) :: [Runlight.JS.Object.t()]
The property names sent with an event in a query's range, most used first.
@spec event_prop_values(t(), map(), String.t(), String.t(), pos_integer()) :: [ Runlight.JS.Object.t() ]
The values one property of an event took, with how often and by how many visitors.
When Runlight itself first counted a visit, leaving out imported history.
When the site's first visit was recorded, or nil with no data yet.
@spec funnel_counts(t(), map(), Runlight.JS.Object.t()) :: [non_neg_integer()]
How many visits reached each step, in order, within the same visit. Step one is the first matching row in the range; each later step must come after the step before it. Filters choose which visits enter the funnel.
@spec funnels(t(), String.t()) :: [Runlight.JS.Object.t()]
@spec goal_breakdown(t(), map(), Runlight.JS.Object.t(), String.t(), pos_integer()) :: [ Runlight.JS.Object.t() ]
A goal's conversions split by where the visit came from, or by the page it happened on.
@spec goal_by_id(t(), String.t()) :: Runlight.JS.Object.t() | nil
@spec goal_series(t(), map(), Runlight.JS.Object.t(), [map()]) :: [ Runlight.JS.Object.t() ]
A goal's conversions and revenue in each bucket, by when each visit started.
@spec goal_totals(t(), map(), Runlight.JS.Object.t()) :: Runlight.JS.Object.t()
One goal's conversions, converting visitors, and revenue for a query's range and filters.
@spec goal_totals_all(t(), map(), [Runlight.JS.Object.t()]) :: %{ required(String.t()) => Runlight.JS.Object.t() }
Every goal's totals in one pass over the range's events, by goal id.
@spec goals(t(), String.t() | nil) :: [Runlight.JS.Object.t()]
Visits by quarter hour since the epoch, which the caller folds into local weekdays and hours, keeping time zones out of SQL.
Records an event; row is a map with the EventRow's fields as atoms in snake case.
@spec insert_link(t(), Runlight.JS.Object.t()) :: :ok
@spec insert_report(t(), Runlight.JS.Object.t()) :: :ok
Records a new session; row is a map with the SessionRow's fields as atoms in snake case.
@spec insert_token(t(), Runlight.JS.Object.t()) :: :ok
@spec journey_pages(t(), map(), pos_integer()) :: %{rows: [map()], sampled: boolean()}
Each visit's pageviews in order, at most per_visit of them, for
journeys. Visits belong to the range they started in.
When the site last recorded a visit, or nil if it never has.
@spec link_breakdown( t(), String.t(), String.t(), integer(), integer(), String.t(), pos_integer() ) :: [ Runlight.JS.Object.t() ]
One link's clicks by a visit dimension.
@spec link_by_id(t(), String.t()) :: Runlight.JS.Object.t() | nil
@spec link_by_slug(t(), String.t()) :: Runlight.JS.Object.t() | nil
The live link with a slug. Slugs are unique across every domain.
@spec link_domains(t()) :: [Runlight.JS.Object.t()]
@spec link_series(t(), String.t(), String.t(), [map()]) :: [Runlight.JS.Object.t()]
One link's clicks per bucket.
@spec links(t(), String.t(), integer(), integer()) :: [Runlight.JS.Object.t()]
A site's links, newest first, with their clicks in a range.
@spec migrate(t()) :: :ok
Creates the tables, and brings an older database up to date. Safe to call any number of times.
@spec new(Runlight.Db.t()) :: t()
A store over a Runlight.Db.
The visitor's open session: any of their hashes, active since since.
Keeps SQLite's planner statistics current, which it never gathers by itself. A sample of each index is enough. Postgres gathers its own.
The pageview an engagement ping or event belongs to, with when its visit started and was last active.
@spec realtime(t(), String.t(), integer()) :: Runlight.JS.Object.t()
Who is on the site now, what they read, and the latest activity.
Puts a period back when its email failed, so the next run tries again.
Removes a domain. Its links keep it as their home and fall back to the app's own link path until it is added again.
@spec report_by(t(), :id | :token, String.t()) :: Runlight.JS.Object.t() | nil
@spec reports(t(), String.t() | nil) :: [Runlight.JS.Object.t()]
The days of a site already built.
The salt for a day, made on first ask. Two racing callers agree on one.
@spec save_funnel(t(), Runlight.JS.Object.t()) :: :ok
@spec save_goal(t(), Runlight.JS.Object.t(), Runlight.JS.Object.t() | nil) :: :ok
@spec series(t(), map(), [map()]) :: [Runlight.JS.Object.t()]
The headline numbers for each bucket.
@spec set_site_overrides(t(), String.t(), Runlight.JS.Object.t()) :: :ok
Every setting whose key starts with a prefix, such as each connected install's.
@spec site_overrides(t()) :: %{required(String.t()) => Runlight.JS.Object.t()}
Settings changed in the dashboard, by site. They win over the ones in code.
@spec sites(t()) :: [Runlight.JS.Object.t()]
The sites kept in the database, by name.
@spec stats(t(), map()) :: Runlight.JS.Object.t()
The headline numbers for a query.
Reads a setting and deletes it. Of two callers at once, only the one whose delete took the row gets its value.
@spec token_by_hash(t(), String.t()) :: Runlight.JS.Object.t() | nil
@spec tokens(t()) :: [Runlight.JS.Object.t()]
Counts a row into its session. An event with reopen false, one that joins
a visit already ended, counts without moving the session's last activity.
After a late event or engagement ping joins an old visit, the day that visit started may already be added up. Forget that day so the next check builds it again.
Runs fun with a store whose every query is in one transaction.
@spec update_link(t(), Runlight.JS.Object.t()) :: :ok
@spec upsert_site(t(), Runlight.JS.Object.t(), integer()) :: :ok
Just the visitor count from stats, in one query, for conversion rates.