Skip to content

Commit dc20faa

Browse files
clockwork-labs-botraincloutiertyler
authored
docs: improve docs and agent discovery metadata (#5243)
## Summary - Fix TypeScript view examples to use `ctx.sender` as a property, matching the server SDK `ViewCtx` API. - Update the architecture overview TypeScript view snippet to use `players.rowType` and `undefined` for optional view returns. - Clarify shared `ViewContext` prose so it does not imply every language uses a callable `ctx.sender()` API. - Improve docs agent-readiness metadata: - publish `/docs/robots.txt` with a docs sitemap directive and Content-Signal policy - add Markdown alternate links for the existing `/docs/llms.txt` and `/docs/llms-full.txt` outputs - generate `/.well-known/agent-skills`-style discovery metadata under `/docs/.well-known/agent-skills/` from the repo's existing `skills/` source files during docs builds ## Validation - `pnpm --dir docs build` - `pnpm --dir docs typecheck` - Verified the docs build emits `robots.txt` and `.well-known/agent-skills/index.json`. - Verified generated Agent Skills SHA-256 digests match the emitted `SKILL.md` artifacts. ## Notes The Cloudflare agent-readiness scan for `spacetimedb.com` still depends on the root/marketing host exposing root-level files such as `/robots.txt`, `/sitemap.xml`, and `/.well-known/agent-skills/index.json`. This PR makes the docs origin produce the corresponding docs-scoped artifacts at `/docs/...`; the root host can route or mirror these if we want the exact `spacetimedb.com` scan to pick them up. --------- Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com> Co-authored-by: rain <rain@rain.local> Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
1 parent 2161148 commit dc20faa

17 files changed

Lines changed: 143 additions & 57 deletions

File tree

docs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# Generated files
88
.docusaurus
99
.cache-loader
10+
static/.well-known/agent-skills/
1011

1112
# Misc
1213
.DS_Store

docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,10 +495,10 @@ A view can be written in a TypeScript module like so:
495495
```typescript
496496
export const my_player = spacetimedb.view(
497497
{ name: 'my_player', public: true },
498-
t.option(players.row()),
498+
t.option(players.rowType),
499499
(ctx) => {
500500
const row = ctx.db.players.identity.find(ctx.sender);
501-
return row ?? null;
501+
return row ?? undefined;
502502
}
503503
);
504504
```

docs/docs/00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ export const create_user = spacetimedb.reducer({ name: t.string(), email: t.stri
3030

3131
// Modify tables
3232
ctx.db.user.insert({
33-
id: 0, // auto-increment will assign
33+
id: 0n, // auto-increment will assign
3434
name,
3535
email
3636
});
3737
});
3838
```
3939

40-
The first argument is the reducer name, the second defines argument types, and the third is the handler function taking `(ctx, args)`.
40+
The exported const name becomes the reducer name. Pass an argument type object followed by a handler function taking `(ctx, args)`. For reducers with no arguments, pass only the handler function.
4141

4242
</TabItem>
4343
<TabItem value="csharp" label="C#">
@@ -127,8 +127,8 @@ SPACETIMEDB_REDUCER(create_user, ReducerContext ctx, std::string name, std::stri
127127
}
128128

129129
// Modify tables
130-
User user{0, name, email}; // 0 for id - auto-increment will assign
131-
ctx.db[user].insert(user);
130+
User new_user{0, name, email}; // 0 for id - auto-increment will assign
131+
ctx.db[user].insert(new_user);
132132

133133
return Ok();
134134
}
@@ -160,7 +160,7 @@ Reducers have full read-write access to all tables (both public and private) thr
160160

161161
```typescript
162162
ctx.db.user.insert({
163-
id: 0, // auto-increment will assign
163+
id: 0n, // auto-increment will assign
164164
name: 'Alice',
165165
email: 'alice@example.com'
166166
});

docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,15 @@ Never use external random number generators (like `Random` in C# without using t
274274
275275
## Module Identity
276276
277-
The context provides access to the module's own identity, which is useful for distinguishing between user-initiated and system-initiated reducer calls.
277+
The context provides access to the module's own identity, which is useful when a reducer needs to refer to the database itself.
278278
279-
This is particularly important for [scheduled reducers](./00300-reducers.md) that should only be invoked by the system, not by external clients.
279+
Scheduled reducers and procedures are private by default in SpacetimeDB 2.x, so you do not need to compare the sender against the module identity to prevent ordinary clients from calling them directly. If you need both a scheduled function and a client-callable entry point, keep the scheduled function private and define a separate public reducer that wraps the shared logic.
280280
281281
<Tabs groupId="server-language" queryString>
282282
<TabItem value="typescript" label="TypeScript">
283283
284284
```typescript
285-
import { schema, table, t, SenderError } from 'spacetimedb/server';
285+
import { schema, table, t } from 'spacetimedb/server';
286286
287287
const scheduledTask = table(
288288
{ name: 'scheduled_task', scheduled: (): any => send_reminder },
@@ -296,12 +296,7 @@ const scheduledTask = table(
296296
const spacetimedb = schema({ scheduledTask });
297297
export default spacetimedb;
298298
299-
export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (ctx, { arg }) => {
300-
// Only allow the scheduler (module identity) to call this
301-
if (ctx.sender != ctx.identity) {
302-
throw new SenderError('This reducer can only be called by the scheduler');
303-
}
304-
299+
export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (_ctx, { arg }) => {
305300
console.log(`Reminder: ${arg.message}`);
306301
});
307302
```
@@ -325,14 +320,8 @@ public static partial class Module
325320
}
326321

327322
[SpacetimeDB.Reducer]
328-
public static void SendReminder(ReducerContext ctx, ScheduledTask task)
323+
public static void SendReminder(ReducerContext _ctx, ScheduledTask task)
329324
{
330-
// Only allow the scheduler (module identity) to call this
331-
if (ctx.Sender != ctx.Identity)
332-
{
333-
throw new Exception("This reducer can only be called by the scheduler");
334-
}
335-
336325
Log.Info($"Reminder: {task.message}");
337326
}
338327
}
@@ -354,12 +343,7 @@ pub struct ScheduledTask {
354343
}
355344

356345
#[reducer]
357-
fn send_reminder(ctx: &ReducerContext, task: ScheduledTask) {
358-
// Only allow the scheduler (module identity) to call this
359-
if ctx.sender() != ctx.identity() {
360-
panic!("This reducer can only be called by the scheduler");
361-
}
362-
346+
fn send_reminder(_ctx: &ReducerContext, task: ScheduledTask) {
363347
spacetimedb::log::info!("Reminder: {}", task.message);
364348
}
365349
```
@@ -383,12 +367,7 @@ FIELD_PrimaryKeyAutoInc(scheduled_task, task_id);
383367
// Register the table for scheduling (column 1 = scheduled_at field, 0-based index)
384368
SPACETIMEDB_SCHEDULE(scheduled_task, 1, send_reminder);
385369

386-
SPACETIMEDB_REDUCER(send_reminder, ReducerContext ctx, ScheduledTask task) {
387-
// Only allow the scheduler (module identity) to call this
388-
if (ctx.sender() != ctx.identity()) {
389-
return Err("This reducer can only be called by the scheduler");
390-
}
391-
370+
SPACETIMEDB_REDUCER(send_reminder, ReducerContext _ctx, ScheduledTask task) {
392371
LOG_INFO("Reminder: " + task.message);
393372
return Ok();
394373
}

docs/docs/00200-core-concepts/00200-functions/00300-reducers/00500-lifecycle.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const init = spacetimedb.init((ctx) => {
2222
console.log('Database initializing...');
2323

2424
// Set up default data
25-
if (ctx.db.settings.count === 0) {
25+
if (ctx.db.settings.count() === 0n) {
2626
ctx.db.settings.insert({
2727
key: 'welcome_message',
2828
value: 'Hello, SpacetimeDB!'
@@ -133,7 +133,7 @@ export const init = spacetimedb.init((ctx) => {
133133
<TabItem value="csharp" label="C#">
134134

135135
```csharp
136-
[SpacetimeDB.Table(Name = "Config")]
136+
[SpacetimeDB.Table(Accessor = "Config")]
137137
public partial struct Config
138138
{
139139
[SpacetimeDB.PrimaryKey]

docs/docs/00200-core-concepts/00200-functions/00300-reducers/00600-error-handling.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Throw a `SenderError`:
2525
import { SenderError } from 'spacetimedb/server';
2626

2727
export const transfer_credits = spacetimedb.reducer(
28-
{ to_user: t.u64(), amount: t.u32() },
28+
{ to_user: t.identity(), amount: t.u32() },
2929
(ctx, { to_user, amount }) => {
30-
const fromUser = ctx.db.users.id.find(ctx.sender);
30+
const fromUser = ctx.db.users.identity.find(ctx.sender);
3131
if (!fromUser) {
3232
throw new SenderError('User not found');
3333
}
@@ -60,9 +60,9 @@ Throw an exception:
6060

6161
```csharp
6262
[SpacetimeDB.Reducer]
63-
public static void TransferCredits(ReducerContext ctx, ulong toUser, uint amount)
63+
public static void TransferCredits(ReducerContext ctx, Identity toUser, uint amount)
6464
{
65-
var fromUser = ctx.Db.User.Id.Find(ctx.Sender);
65+
var fromUser = ctx.Db.User.Identity.Find(ctx.Sender);
6666
if (fromUser == null)
6767
{
6868
throw new InvalidOperationException("User not found");
@@ -86,13 +86,13 @@ Return an error:
8686
#[reducer]
8787
pub fn transfer_credits(
8888
ctx: &ReducerContext,
89-
to_user: u64,
89+
to_user: Identity,
9090
amount: u32
9191
) -> Result<(), String> {
92-
let from_balance = ctx.db.users().id().find(ctx.sender.identity)
93-
.ok_or("User not found");
92+
let from_user = ctx.db.users().identity().find(ctx.sender())
93+
.ok_or("User not found")?;
9494

95-
if from_balance.credits < amount {
95+
if from_user.credits < amount {
9696
return Err("Insufficient credits".to_string());
9797
}
9898

docs/docs/00200-core-concepts/00200-functions/00500-views.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const my_player = spacetimedb.view(
5757
{ name: 'my_player', public: true },
5858
t.option(players.rowType),
5959
(ctx) => {
60-
const row = ctx.db.players.identity.find(ctx.sender());
60+
const row = ctx.db.players.identity.find(ctx.sender);
6161
return row ?? undefined;
6262
}
6363
);
@@ -245,7 +245,7 @@ struct Player {
245245
};
246246
SPACETIMEDB_STRUCT(Player, id, identity, name)
247247
SPACETIMEDB_TABLE(Player, player, Public)
248-
FIELD_PrimaryKeyAuto(player, id)
248+
FIELD_PrimaryKeyAutoInc(player, id)
249249
FIELD_Unique(player, identity)
250250

251251
struct PlayerLevel {
@@ -294,7 +294,7 @@ Views can return `std::optional<T>` for at-most-one row, `std::vector<T>` for mu
294294
295295
Views use one of two context types:
296296
297-
- **`ViewContext`**: Provides access to the caller's `Identity` through `ctx.sender()`. Use this when the view depends on who is querying it.
297+
- **`ViewContext`**: Provides access to the caller's `Identity` through the context's sender field or method. Use this when the view depends on who is querying it.
298298
- **`AnonymousViewContext`**: Does not provide caller information. Use this when the view produces the same results regardless of who queries it.
299299
300300
Both contexts provide read-only access to tables and indexes through `ctx.db`.
@@ -305,7 +305,7 @@ The choice between `ViewContext` and `AnonymousViewContext` has significant perf
305305
306306
**Anonymous views can be shared across all subscribers.** When a view uses `AnonymousViewContext`, SpacetimeDB knows the result is the same for every client. The database can materialize the view once and serve that same result to all subscribers. When the underlying data changes, it recomputes the view once and broadcasts the update to everyone.
307307
308-
**Per-user views require separate computation for each subscriber.** When a view uses `ViewContext` and invokes `ctx.sender()`, each client potentially sees different data. SpacetimeDB must compute and track the view separately for each subscriber. With 1,000 connected users, that's 1,000 separate view computations and 1,000 separate sets of change tracking.
308+
**Per-user views require separate computation for each subscriber.** When a view uses `ViewContext` and reads the caller's sender identity, each client potentially sees different data. SpacetimeDB must compute and track the view separately for each subscriber. With 1,000 connected users, that's 1,000 separate view computations and 1,000 separate sets of change tracking.
309309
310310
**Prefer `AnonymousViewContext` when possible.** Design your views to be caller-independent when the use case allows. For example:
311311

docs/docs/00200-core-concepts/00300-tables.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ Use idiomatic naming conventions for each language:
320320
| Language | Convention | Example Table | Example Accessor |
321321
|----------|------------|---------------|------------------|
322322
| **TypeScript** | snake_case | `'player_score'` | `ctx.db.playerScore` |
323-
| **C#** | PascalCase | `Name = "PlayerScore"` | `ctx.Db.PlayerScore` |
323+
| **C#** | PascalCase | `Accessor = "PlayerScore"` | `ctx.Db.PlayerScore` |
324324
| **Rust** | lower_snake_case | `name = player_score` | `ctx.db.player_score()` |
325325
| **C++** | lower_snake_case | `player_score` | `ctx.db[player_score]` |
326326

docs/docs/00200-core-concepts/00300-tables/00210-file-storage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export const register_document = spacetimedb.reducer({
211211
storageUrl: t.string(),
212212
}, (ctx, { filename, mimeType, sizeBytes, storageUrl }) => {
213213
ctx.db.document.insert({
214-
id: 0, // auto-increment
214+
id: 0n, // auto-increment
215215
ownerId: ctx.sender,
216216
filename,
217217
mimeType,

docs/docs/00200-core-concepts/00300-tables/00400-access-permissions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ Reducers receive a `ReducerContext` which provides full read-write access to all
136136
<TabItem value="typescript" label="TypeScript">
137137
138138
```typescript
139-
export const example = spacetimedb.reducer({}, (ctx) => {
139+
export const example = spacetimedb.reducer((ctx) => {
140140
// Insert
141-
ctx.db.user.insert({ id: 0, name: 'Alice', email: 'alice@example.com' });
141+
ctx.db.user.insert({ id: 0n, name: 'Alice', email: 'alice@example.com' });
142142
143143
// Read: iterate all rows
144144
for (const user of ctx.db.user.iter()) {

0 commit comments

Comments
 (0)