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/2. Either takes prefix:, what every
table and index name starts with in place of rl_, so two apps sharing one
database each keep a set of their own: up to 32 letters, digits, and
underscores, not starting with a digit.
Summary
Types
A store. prefix is what its table and index names start with: rl_ unless it was given another.
funnel_visits is the most visits a funnel is counted over; smaller in tests, to try the cap.
Functions
Writes a setting only if it is not there yet. Of two callers at once, only one gets true.
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.
What every table and index name starts with, unless a store is given another prefix in its place.
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 but those of these days, so the hashes made with them can never be made again.
A store over an app's Ecto repo; see Runlight.Db.Ecto for the options,
and new/2 for prefix:.
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.
Past funnel_visits visits in the range, only the newest are counted, and
sampled says so.
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.
Seeks one matching event per hostname, skipping its remaining events before the next seek.
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.
How long the stores wait for the lock for creating tables, in milliseconds:
30 seconds, unless a test has set another with lock_wait_in_tests/1.
Creates the tables, and brings an older database up to date. Safe to call any number of times.
A store over a Runlight.Db. prefix: takes the place of rl_ at the
start of every table and index name, so two apps can each keep Runlight's
tables in one database. A Db that is already prefixed keeps its own.
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.
What the store's table and index names start with: rl_ unless it was given another.
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.
A setting that is made once: every caller gets the value written first, whoever made it.
Every setting whose key starts with a prefix, such as each connected install's.
After opening a visit: the visitor's earliest visit still going, which is this one unless another process opened one for the same visitor at the same moment. Then both settle on the earliest, and the one that is not drops its own, before anything is counted into it.
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.
Changes a setting only if it still holds from. Of two callers at once, only one gets true.
Runlight's tables, after the prefix.
The store's tables as named in the database, for an app removing Runlight: each one by name, since another app's tables may well start with the same letters.
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.
Whether a visit to one of these sites that started in a stretch of time was still going at since.
Just the visitor count from stats, in one query, for conversion rates.
Types
@type t() :: %Runlight.Store{ db: Runlight.Db.t(), funnel_visits: pos_integer(), prefix: String.t() }
A store. prefix is what its table and index names start with: rl_ unless it was given another.
funnel_visits is the most visits a funnel is counted over; smaller in tests, to try the cap.
Functions
Writes a setting only if it is not there yet. Of two callers at once, only one gets true.
@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.
@spec default_prefix() :: String.t()
What every table and index name starts with, unless a store is given another prefix in its place.
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 but those of these days, so the hashes made with them can never be made again.
A store over an app's Ecto repo; see Runlight.Db.Ecto for the options,
and new/2 for prefix:.
@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()) :: %{ counts: [non_neg_integer()], sampled: boolean() }
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.
Past funnel_visits visits in the range, only the newest are counted, and
sampled says so.
@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()]) :: 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()]
@spec hostname_suggestions(t(), map(), pos_integer()) :: [Runlight.JS.Object.t()]
Seeks one matching event per hostname, skipping its remaining events before the next seek.
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 lock_wait_ms() :: pos_integer()
How long the stores wait for the lock for creating tables, in milliseconds:
30 seconds, unless a test has set another with lock_wait_in_tests/1.
@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(), keyword()) :: t()
A store over a Runlight.Db. prefix: takes the place of rl_ at the
start of every table and index name, so two apps can each keep Runlight's
tables in one database. A Db that is already prefixed keeps its own.
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.
With only_when_stale, only when there are none, or the events have more
than doubled since they were gathered. Statistics gathered on a new, empty
database say every table is empty, and a goal over a million visits then
took a minute rather than milliseconds.
The pageview an engagement ping or event belongs to, with when its visit started and was last active.
What the store's table and index names start with: rl_ unless it was given another.
@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
A setting that is made once: every caller gets the value written first, whoever made it.
Every setting whose key starts with a prefix, such as each connected install's.
After opening a visit: the visitor's earliest visit still going, which is this one unless another process opened one for the same visitor at the same moment. Then both settle on the earliest, and the one that is not drops its own, before anything is counted into it.
@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.
Changes a setting only if it still holds from. Of two callers at once, only one gets true.
@spec table_names() :: [String.t()]
Runlight's tables, after the prefix.
The store's tables as named in the database, for an app removing Runlight: each one by name, since another app's tables may well start with the same letters.
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
Whether a visit to one of these sites that started in a stretch of time was still going at since.
Just the visitor count from stats, in one query, for conversion rates.