diff --git a/docs/.gitignore b/docs/.gitignore index b2d6de30624..545105cda9a 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -7,6 +7,7 @@ # Generated files .docusaurus .cache-loader +static/.well-known/agent-skills/ # Misc .DS_Store diff --git a/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md b/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md index 61a70b681e7..487b962f4ad 100644 --- a/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md +++ b/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md @@ -495,10 +495,10 @@ A view can be written in a TypeScript module like so: ```typescript export const my_player = spacetimedb.view( { name: 'my_player', public: true }, - t.option(players.row()), + t.option(players.rowType), (ctx) => { const row = ctx.db.players.identity.find(ctx.sender); - return row ?? null; + return row ?? undefined; } ); ``` diff --git a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md index 4338f0083a2..ac5374516e5 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md +++ b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md @@ -274,15 +274,15 @@ Never use external random number generators (like `Random` in C# without using t ## Module Identity -The context provides access to the module's own identity, which is useful for distinguishing between user-initiated and system-initiated reducer calls. +The context provides access to the module's own identity, which is useful when a reducer needs to refer to the database itself. -This is particularly important for [scheduled reducers](./00300-reducers.md) that should only be invoked by the system, not by external clients. +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. ```typescript -import { schema, table, t, SenderError } from 'spacetimedb/server'; +import { schema, table, t } from 'spacetimedb/server'; const scheduledTask = table( { name: 'scheduled_task', scheduled: (): any => send_reminder }, @@ -296,12 +296,7 @@ const scheduledTask = table( const spacetimedb = schema({ scheduledTask }); export default spacetimedb; -export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (ctx, { arg }) => { - // Only allow the scheduler (module identity) to call this - if (ctx.sender != ctx.identity) { - throw new SenderError('This reducer can only be called by the scheduler'); - } - +export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (_ctx, { arg }) => { console.log(`Reminder: ${arg.message}`); }); ``` @@ -325,14 +320,8 @@ public static partial class Module } [SpacetimeDB.Reducer] - public static void SendReminder(ReducerContext ctx, ScheduledTask task) + public static void SendReminder(ReducerContext _ctx, ScheduledTask task) { - // Only allow the scheduler (module identity) to call this - if (ctx.Sender != ctx.Identity) - { - throw new Exception("This reducer can only be called by the scheduler"); - } - Log.Info($"Reminder: {task.message}"); } } @@ -354,12 +343,7 @@ pub struct ScheduledTask { } #[reducer] -fn send_reminder(ctx: &ReducerContext, task: ScheduledTask) { - // Only allow the scheduler (module identity) to call this - if ctx.sender() != ctx.identity() { - panic!("This reducer can only be called by the scheduler"); - } - +fn send_reminder(_ctx: &ReducerContext, task: ScheduledTask) { spacetimedb::log::info!("Reminder: {}", task.message); } ``` @@ -383,12 +367,7 @@ FIELD_PrimaryKeyAutoInc(scheduled_task, task_id); // Register the table for scheduling (column 1 = scheduled_at field, 0-based index) SPACETIMEDB_SCHEDULE(scheduled_task, 1, send_reminder); -SPACETIMEDB_REDUCER(send_reminder, ReducerContext ctx, ScheduledTask task) { - // Only allow the scheduler (module identity) to call this - if (ctx.sender() != ctx.identity()) { - return Err("This reducer can only be called by the scheduler"); - } - +SPACETIMEDB_REDUCER(send_reminder, ReducerContext _ctx, ScheduledTask task) { LOG_INFO("Reminder: " + task.message); return Ok(); } diff --git a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00500-lifecycle.md b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00500-lifecycle.md index 5a9b58e9adf..b679d90ef82 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00500-lifecycle.md +++ b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00500-lifecycle.md @@ -22,7 +22,7 @@ export const init = spacetimedb.init((ctx) => { console.log('Database initializing...'); // Set up default data - if (ctx.db.settings.count === 0) { + if (ctx.db.settings.count() === 0n) { ctx.db.settings.insert({ key: 'welcome_message', value: 'Hello, SpacetimeDB!' @@ -133,7 +133,7 @@ export const init = spacetimedb.init((ctx) => { ```csharp -[SpacetimeDB.Table(Name = "Config")] +[SpacetimeDB.Table(Accessor = "Config")] public partial struct Config { [SpacetimeDB.PrimaryKey] diff --git a/docs/docs/00200-core-concepts/00200-functions/00500-views.md b/docs/docs/00200-core-concepts/00200-functions/00500-views.md index 65f2fb7bab4..20295b4ad75 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00500-views.md +++ b/docs/docs/00200-core-concepts/00200-functions/00500-views.md @@ -57,7 +57,7 @@ export const my_player = spacetimedb.view( { name: 'my_player', public: true }, t.option(players.rowType), (ctx) => { - const row = ctx.db.players.identity.find(ctx.sender()); + const row = ctx.db.players.identity.find(ctx.sender); return row ?? undefined; } ); @@ -245,7 +245,7 @@ struct Player { }; SPACETIMEDB_STRUCT(Player, id, identity, name) SPACETIMEDB_TABLE(Player, player, Public) -FIELD_PrimaryKeyAuto(player, id) +FIELD_PrimaryKeyAutoInc(player, id) FIELD_Unique(player, identity) struct PlayerLevel { @@ -294,7 +294,7 @@ Views can return either `std::optional` for at-most-one row or `std::vector a.name.localeCompare(b.name) +)) { + if (!entry.isDirectory()) { + continue; + } + + const skillPath = path.join(sourceDir, entry.name, 'SKILL.md'); + const markdown = await readFile(skillPath, 'utf8'); + const metadata = readFrontmatter(markdown, skillPath); + + const skillOutputDir = path.join(outputDir, metadata.name); + await mkdir(skillOutputDir, { recursive: true }); + await writeFile(path.join(skillOutputDir, 'SKILL.md'), markdown); + + skills.push({ + name: metadata.name, + type: 'skill-md', + description: metadata.description, + url: `/docs/.well-known/agent-skills/${metadata.name}/SKILL.md`, + digest: `sha256:${createHash('sha256').update(markdown).digest('hex')}`, + }); +} + +await writeFile( + path.join(outputDir, 'index.json'), + `${JSON.stringify( + { + $schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json', + skills, + }, + null, + 2 + )}\n` +); + diff --git a/docs/static/robots.txt b/docs/static/robots.txt new file mode 100644 index 00000000000..5e46fde337c --- /dev/null +++ b/docs/static/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Content-Signal: search=yes, ai-input=yes, ai-train=no + +Sitemap: https://spacetimedb.com/docs/sitemap.xml diff --git a/skills/csharp-server/SKILL.md b/skills/csharp-server/SKILL.md index f758a98c58b..05f353d5802 100644 --- a/skills/csharp-server/SKILL.md +++ b/skills/csharp-server/SKILL.md @@ -92,7 +92,7 @@ public ulong AuthorId; // Multi-column (struct-level): [SpacetimeDB.Table(Accessor = "Membership")] -[SpacetimeDB.Index.BTree(Accessor = "ByGroupUser", Columns = ["GroupId", "UserId"])] +[SpacetimeDB.Index.BTree(Accessor = "ByGroupUser", Columns = new[] { nameof(GroupId), nameof(UserId) })] public partial struct Membership { public ulong GroupId; public Identity UserId; ... } ``` @@ -207,7 +207,7 @@ public static void Tick(ReducerContext ctx, TickTimer timer) } // One-time: fires once at a specific time -var at = new ScheduleAt.Time(DateTimeOffset.UtcNow.AddSeconds(10)); +var at = new ScheduleAt.Time(ctx.Timestamp + new TimeDuration(10_000_000)); // Repeating: fires on an interval var at = new ScheduleAt.Interval(TimeSpan.FromSeconds(5));