Whose laev this is.
Everything laev knows — your keys, your marks, your watchlist — used to be global: one data directory, one Real-Debrid token, one named ETS table, all read straight out of the application environment. That is exactly right for a command somebody runs on their own machine, and exactly wrong for a server where two people are logged in at once, where it would mean one account's token fetching another account's film.
A scope is a user's half of that: a directory to keep their state in, and the keys to the services they have connected. It is bound to the process rather than passed through every function, because the alternative is a parameter on several hundred signatures, and because the BEAM already gives us the isolation for free — Phoenix runs each request and each LiveView in its own process, so binding there is the same thing as binding per user.
Nothing is bound by default, and everything falls back to the application environment, so the CLI goes on behaving exactly as it did: one person, one machine, no scope at all.
The one place this leaks is work handed to another process, which does not
inherit a process dictionary. async_stream/3, async/1 and spawn/1 here
carry the scope across for you; Task.async and spawn directly do not,
which is the single rule to remember when adding work that runs elsewhere.
Summary
Functions
Task.async, with the scope carried across.
Task.async_stream, with this process's scope bound inside each task.
Unbind.
The scope bound to this process, or nil when there isn't one.
Where this user's state lives.
A setting, for whoever this process belongs to.
A scope over a directory, with whatever that user has already saved in it.
A scope over a directory, with the keys that belong to whoever owns it.
Bind a scope to this process for good — for a LiveView's mount.
Run fun with scope bound, restoring whatever was bound before.
Change a setting for whoever this process belongs to.
spawn, with the scope carried across.
A name unique to this scope, for the things that must not be shared by name — the ETS tables holding the watchlist and the history.
Types
Functions
Task.async, with the scope carried across.
Task.async_stream, with this process's scope bound inside each task.
Unbind.
The scope bound to this process, or nil when there isn't one.
Where this user's state lives.
A setting, for whoever this process belongs to.
The scope's own value where it has one, the application environment otherwise — which is every value when nothing is bound, and is why the CLI does not know this module exists.
A scope over a directory, with whatever that user has already saved in it.
How a request should build one: the settings a user changed last time are in their own directory, and anything the server holds for them — a token kept in a database rather than on disk, say — is passed in and wins.
A scope over a directory, with the keys that belong to whoever owns it.
config holds the same names the config file uses — :rd_token,
:tmdb_key, :torbox_api_key and the rest — and anything absent from it
falls back to the application environment, so a server can share, say, one
TMDB key across everybody while each person brings their own debrid account.
Bind a scope to this process for good — for a LiveView's mount.
Run fun with scope bound, restoring whatever was bound before.
The way a web request should enter laev: bind once at the edge, and every call underneath is that user's without being told so.
Change a setting for whoever this process belongs to.
Within a scope it changes that scope and nothing else — one user turning on VLC is not every user turning on VLC. Unscoped it is the application environment, which is what makes the Settings screen apply immediately.
spawn, with the scope carried across.
A name unique to this scope, for the things that must not be shared by name — the ETS tables holding the watchlist and the history.