You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/docs/00200-core-concepts/00200-functions/00500-views.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -743,6 +743,8 @@ You may notice that views can only access table data through indexed lookups (`.
743
743
744
744
**Why SQL subscriptions can scan.** You might wonder why SQL subscription queries can include full table scans while view functions cannot. The difference is that SQL queries are not black boxes - SpacetimeDB can analyze and transform them. The query engine uses **incremental evaluation**: when rows change, it computes exactly which output rows are affected without re-running the entire query. Think of it like taking the derivative of the query - given a small change in input, compute the small change in output. Since view functions are opaque code, this kind of incremental computation isn't possible.
745
745
746
+
**Why query builder subscriptions can scan.** For the same reason that SQL subscriptions can scan. Anything you can do with SQL subscriptions you can do with the query builder API and vice versa.
747
+
746
748
**The tradeoff is acceptable for indexed access.** For point lookups (`.find()`) and small range scans (`.filter()` on indexed columns), the performance difference between full re-evaluation and incremental evaluation is small. This is why views are limited to indexed access - it's the subset of operations where the black-box limitation doesn't hurt performance.
747
749
748
750
If you need to aggregate or sort entire tables, consider returning a `Query` from your view instead. Since queries can be analyzed by the query engine, they support incremental evaluation even when scanning full tables. Alternatively, design your schema so the data you need is accessible through indexes.
println!("Subscription ready with {} users", ctx.db().user().count());
159
+
})
160
+
.on_error(|ctx, error| {
161
+
eprintln!("Subscription failed: {}", error);
162
+
})
163
+
.add_query(|ctx|ctx.from.user())
164
+
.subscribe();
165
+
```
166
+
167
+
</TabItem>
168
+
</Tabs>
169
+
113
170
See the [Subscriptions documentation](/subscriptions) for detailed information on subscription queries and semantics. Subscribe to [tables](/tables) for row data, or to [views](/functions/views) for computed query results.
Copy file name to clipboardExpand all lines: docs/static/ai-rules/spacetimedb-typescript.mdc
+16-5Lines changed: 16 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -422,11 +422,8 @@ conn.subscriptionBuilder()
422
422
423
423
> ⚠️ **Do NOT use Row Level Security (RLS)** — it is deprecated.
424
424
425
-
⚠️ **CRITICAL: Views SHOULD use index lookups, NOT `.iter()` (performance)**
426
-
427
-
Using `.iter()` in views is technically possible but causes severe performance issues:
428
-
- Every change to ANY row in the table triggers a full re-evaluation of the view
429
-
- For large tables, this becomes prohibitively expensive
425
+
> ⚠️ **CRITICAL:** Procedural views (views that compute results in code) can ONLY access data via index lookups, NOT `.iter()`.
426
+
> If you need a view that scans/filters across many rows (including the entire table), return a **query** built with the query builder (`ctx.from...`).
430
427
431
428
```typescript
432
429
// Private table with index on ownerId
@@ -456,6 +453,20 @@ spacetimedb.view(
456
453
);
457
454
```
458
455
456
+
### Query builder view pattern (can scan)
457
+
458
+
```typescript
459
+
// Query-builder views return a query; the SQL engine maintains the result incrementally.
460
+
// This can scan the whole table if needed (e.g. leaderboard-style queries).
461
+
spacetimedb.anonymousView(
462
+
{ name: 'top_players', public: true },
463
+
t.array(Player.rowType),
464
+
(ctx) =>
465
+
ctx.from.player
466
+
.where(p => p.score.gt(1000))
467
+
);
468
+
```
469
+
459
470
### ViewContext vs AnonymousViewContext
460
471
```typescript
461
472
// ViewContext — has ctx.sender, result varies per user (computed per-subscriber)
0 commit comments