Module Defined Views
The shipping continues! This time we have Module Defined Views or just "views". Views are a simple, but incredibly expressive way to define custom and intricate read permissioning for your tables.
Views, which are inspired by a similar concept from SQL, are virtual tables that are defined by new "view functions" in your module and derived from other tables or parameters.
Views are defined by read-only procedural, functions in the language of your module. This function returns data derived from your database tables. You can then query and subscribe to this view as you would any normal database table and it will be updated automatically in realtime.
Here's a look at the syntax for defining a view in the various module languages:
Rust
Declare with #[view]. First argument is a view context (&ViewContext or &AnonymousViewContext). Return Option<T> (0–1 row) or Vec<T> (many rows).
#[view(name = my_player, public)]
fn my_player(ctx: &ViewContext) -> Option<Player> {
ctx.db.player().identity().find(ctx.sender)
}
#[view(name = players_for_level, public)]
fn players_for_level(ctx: &AnonymousViewContext) -> Vec<Player> {
ctx.db
.player_level()
.level()
.filter(2u64) // players for level 2
.map(|player| {
ctx.db
.player()
.id()
.find(player.player_id)
})
.collect()
}Notes
- A
nameis required. - Only the context parameter is allowed; no extra args (yet).
- The context provides a read-only view of the database
- Mutations are not allowed
- Full table scans are not allowed
C#
Use [SpacetimeDB.View] with ViewContext or AnonymousViewContext. Return a single row as T? or many rows as List<T> / T[].
[SpacetimeDB.View(Name = "my_player", Public = true)]
public static Player? MyPlayer(ViewContext ctx) =>
ctx.Db.Player.Identity.Find(ctx.Sender) as Player;
[SpacetimeDB.View(Name = "players_for_level", Public = true)]
public static List<Player> PlayerLocations(AnonymousViewContext ctx) {
var rows = new List<Player>();
foreach (var player in ctx.Db.PlayerLevel.Level.Filter(2))
{
if (ctx.Db.Player.Id.Find(player.PlayerId) is Player p)
{
rows.Add(p);
}
}
return rows;
}TypeScript
Register with schema.view(...) or schema.anonymousView(...). Use t.option(row) for 0–1 row or t.array(row) for many rows.
spacetimedb.view(
{ name: 'my_player', public: true },
t.option(players.row()),
(ctx) => {
return ctx.db.players.identity.find(ctx.sender) ?? null;
}
);
spacetimedb.anonymousView(
{ name: 'players_for_level', public: true },
t.option(players.row()),
(ctx) => {
const out = [];
for (const pl of ctx.db.playerLevels.level.find(2)) {
const p = ctx.db.players.id.find(pl.player_id);
if (p) out.push(p);
}
return out;
}
);Row-level security rules
Currently procedurally defined view functions are limited to index probing tables so that we can efficiently compute the real-time delta for procedural functions. However, we also plan to shortly add the ability to return typed queries from view functions which will allow you to define performant, incrementally evaluated queries which execute full tables scans.
This functionality will make views strictly more expressive and powerful than the existing unstable RLS (row-level security) rules API that we introduced earlier this year. As such we will be deprecating the RLS API in favor of the view API. Here is an idea (not final API) of what that might look like in TypeScript
spacetimedb.view(
{ name: 'high_level_players', public: true },
t.query(players.row()),
(ctx) => {
return ctx.from(ctx.db.player).where(player => gt(player.level, 50))
}
);What's Changed
- Add typescript quickstart smoketest by @coolreader18 in #3463
- CI - Move the Package job to ubuntu-latest by @bfops in #3553
- CI - Fix NuGet smoketest dependencies by @bfops in #3557
- Fix Nix build by not using Git in Cargo build scripts by @gefjon in #3551
- Use production API key by @JulienLavocat in #3560
- Bump versions to 1.7.0 by @bfops in #3550
- Generate client bindings for views by @joshua-spacetime in #3564
- Add proper V8 sys module versioning by @coolreader18 in #3527
- Set default server to
localin replication smoketests by @gefjon in #3562 - View resolution in sql by @joshua-spacetime in #3570
- [teams 1/5] Reset database by @kim in #3496
- Fix some annoyances with the smoketests by @coolreader18 in #3556
- Add view handling to query engine and planner by @joshua-spacetime in #3578
- CI - Fix the merge queue by @bfops in #3571
- Revert "[teams 1/5] Reset database (#3496)" by @bfops in #3580
- Decrement view subscriber count on disconnect by @joshua-spacetime in #3547
- Update C# client-api bindings by @gefjon in #3537
- Views: Host interface for WASM modules by @Shubham8287 in #3548
- Fix for multi-column indexes in typescript modules by @jsdt in #3589
- smoketests: Adjust enable replication tests by @kim in #3590
Dockerfilefails more cleanly if.gitis a submodule by @bfops in #3591- fix: view index by @Shubham8287 in #3596
- remove module watcher from subscription by @Shubham8287 in #3602
- Fix issue with row id for non primary key tables by @aasoni in #3603
- Typescript views by @coolreader18 in #3584
- CI - Make Internal Tests less brittle by @bfops in #3536
- Materialize views on subscribe by @joshua-spacetime in #3599
- Refactor module instance code by @coolreader18 in #3605
- commitlog,durability: Support preallocation of disk space by @kim in #3437
- Add Rust client SDK bindings for calling procedures by @gefjon in #3532
- Add comment about public API key use on Docusaurus by @JulienLavocat in #3567
- Views: fix table schema creation from system table. by @Shubham8287 in #3615
- Disable parameterized views in rust by @joshua-spacetime in #3629
- CI - Fix format strings by @bfops in #3627
- Atomic view update by @joshua-spacetime in #3624
- Disconnect clients when updating RLS rules by @mamcx in #3574
- [teams 1/5] Reset database by @kim in #3611
- SQL smoketests for views by @Shubham8287 in #3616
- [teams 2/5] client-api: Add
parentparameter to publish endpoint by @kim in #3519 - [teams 3/5] API authorization, CLI, smoketests by @kim in #3523
- C# bindings for views by @rekhoff in #3585
Full Changelog: v1.7.0...v1.8.0