mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-15 20:28:04 -04:00
210 lines
53 KiB
HTML
210 lines
53 KiB
HTML
<!doctype html>
|
||
<html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-Server Module Languages/CSharp-Quickstart" data-has-hydrated="false">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="generator" content="Docusaurus v3.9.1">
|
||
<title data-rh="true">C# Quickstart | SpacetimeDB docs</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://docs.spacetimedb.com/modules/c-sharp/quickstart"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="C# Quickstart | SpacetimeDB docs"><meta data-rh="true" name="description" content="Module Quickstart"><meta data-rh="true" property="og:description" content="Module Quickstart"><link data-rh="true" rel="icon" href="/images/favicon.ico"><link data-rh="true" rel="canonical" href="https://docs.spacetimedb.com/modules/c-sharp/quickstart"><link data-rh="true" rel="alternate" href="https://docs.spacetimedb.com/modules/c-sharp/quickstart" hreflang="en"><link data-rh="true" rel="alternate" href="https://docs.spacetimedb.com/modules/c-sharp/quickstart" hreflang="x-default"><link data-rh="true" rel="preconnect" href="https://QBC7Z9KXS2-dsn.algolia.net" crossorigin="anonymous"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"C# Quickstart","item":"https://docs.spacetimedb.com/modules/c-sharp/quickstart"}]}</script><link rel="search" type="application/opensearchdescription+xml" title="SpacetimeDB docs" href="/opensearch.xml"><link rel="stylesheet" href="/assets/css/styles.0cb9f7a7.css">
|
||
<script src="/assets/js/runtime~main.d71df0f5.js" defer="defer"></script>
|
||
<script src="/assets/js/main.31d95d83.js" defer="defer"></script>
|
||
</head>
|
||
<body class="navigation-with-keyboard">
|
||
<svg style="display: none;"><defs>
|
||
<symbol id="theme-svg-external-link" viewBox="0 0 24 24"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></symbol>
|
||
</defs></svg>
|
||
<script>document.documentElement.setAttribute("data-theme","light"),document.documentElement.setAttribute("data-theme-choice","light"),function(){try{const c=new URLSearchParams(window.location.search).entries();for(var[t,e]of c)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><link rel="preload" as="image" href="https://spacetimedb.com/images/brand.png"><div role="region" aria-label="Skip to main content"><a class="skipToContent_6jFv" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="theme-layout-navbar navbar navbar--fixed-top"><div class="navbar__inner"><div class="theme-layout-navbar-left navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/"><div class="navbar__logo"><img src="https://spacetimedb.com/images/brand.png" alt="SpacetimeDB Logo" class="themedComponent_rvet themedComponent--light_mbAJ"><img src="https://spacetimedb.com/images/brand.png" alt="SpacetimeDB Logo" class="themedComponent_rvet themedComponent--dark_Ncy6"></div></a><div class="navbarSearchContainer_AesG"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search (Command+K)"><span class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"></span></button></div></div><div class="theme-layout-navbar-right navbar__items navbar__items--right"><a href="https://spacetimedb.com/install" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Install</a><a href="https://spacetimedb.com/pricing" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Pricing</a><a href="https://spacetimedb.com/maincloud" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Maincloud</a><a href="https://spacetimedb.com/blog" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Blog</a><a href="https://spacetimedb.com/community" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Community</a></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="theme-layout-main main-wrapper mainWrapper_hV_y"><div class="docsWrapper_f07g"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_MJiz" type="button"></button><div class="docRoot_Gd2s"><aside class="theme-doc-sidebar-container docSidebarContainer_fSpF"><div class="sidebarViewport_YElg"><div class="sidebar_kjg4"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_AG0n"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/"><span title="Intro" class="categoryLinkLabel_EDYQ">Intro</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/deploying/maincloud"><span title="Deploying" class="categoryLinkLabel_EDYQ">Deploying</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/unity"><span title="Unity tutorial" class="categoryLinkLabel_EDYQ">Unity tutorial</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/unreal"><span title="Unreal Tutorial" class="categoryLinkLabel_EDYQ">Unreal Tutorial</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/cli-reference"><span title="CLI Reference" class="categoryLinkLabel_EDYQ">CLI Reference</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" role="button" aria-expanded="true" href="/modules"><span title="Server Module Languages" class="categoryLinkLabel_EDYQ">Server Module Languages</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/modules"><span title="Overview" class="linkLabel_dpMB">Overview</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/modules/rust/quickstart"><span title="Rust Quickstart" class="linkLabel_dpMB">Rust Quickstart</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/modules/rust"><span title="Rust Reference" class="linkLabel_dpMB">Rust Reference</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/modules/c-sharp/quickstart"><span title="C# Quickstart" class="linkLabel_dpMB">C# Quickstart</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/modules/c-sharp"><span title="C# Reference" class="linkLabel_dpMB">C# Reference</span></a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/sdks"><span title="Client SDK Languages" class="categoryLinkLabel_EDYQ">Client SDK Languages</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/sql"><span title="SQL" class="categoryLinkLabel_EDYQ">SQL</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/subscriptions"><span title="Subscriptions" class="categoryLinkLabel_EDYQ">Subscriptions</span></a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rls"><span title="Row Level Security" class="linkLabel_dpMB">Row Level Security</span></a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/how-to/incremental-migrations"><span title="How-To" class="categoryLinkLabel_EDYQ">How-To</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/spacetimeauth"><span title="SpacetimeAuth" class="categoryLinkLabel_EDYQ">SpacetimeAuth</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/http/authorization"><span title="HTTP API" class="categoryLinkLabel_EDYQ">HTTP API</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_ggI5 menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/webassembly-abi"><span title="Internals" class="categoryLinkLabel_EDYQ">Internals</span></a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/appendix"><span title="Appendix" class="linkLabel_dpMB">Appendix</span></a></li></ul></nav></div></div></aside><main class="docMainContainer_dkUT"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_w2oE"><div class="docItemContainer_f71m"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer_xsLZ" aria-label="Breadcrumbs"><ul class="breadcrumbs"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_oyay"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li class="breadcrumbs__item"><span class="breadcrumbs__link">Server Module Languages</span></li><li class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link">C# Quickstart</span></li></ul></nav><div class="tocCollapsible_dqme theme-doc-toc-mobile tocMobile_Z34P"><button type="button" class="clean-btn tocCollapsibleButton_QMSE">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>C# Module Quickstart</h1></header>
|
||
<p>In this tutorial, we'll implement a simple chat server as a SpacetimeDB module.</p>
|
||
<p>A SpacetimeDB module is code that gets compiled to WebAssembly and is uploaded to SpacetimeDB. This code becomes server-side logic that interfaces directly with the Spacetime relational database.</p>
|
||
<p>Each SpacetimeDB module defines a set of tables and a set of reducers.</p>
|
||
<p>Each table is defined as a C# <code>class</code> annotated with <code>[SpacetimeDB.Table]</code>, where an instance represents a row, and each field represents a column.
|
||
By default, tables are <strong>private</strong>. This means that they are only readable by the table owner, and by server module code.
|
||
The <code>[SpacetimeDB.Table(Public = true))]</code> annotation makes a table public. <strong>Public</strong> tables are readable by all users, but can still only be modified by your server module code.</p>
|
||
<p>A reducer is a function which traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In C#, reducers are defined as functions annotated with <code>[SpacetimeDB.Reducer]</code>. If an exception is thrown, the reducer call fails, the database is not updated, and a failed message is reported to the client.</p>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="install-spacetimedb">Install SpacetimeDB<a href="#install-spacetimedb" class="hash-link" aria-label="Direct link to Install SpacetimeDB" title="Direct link to Install SpacetimeDB" translate="no"></a></h2>
|
||
<p>If you haven't already, start by <a href="https://spacetimedb.com/install" target="_blank" rel="noopener noreferrer">installing SpacetimeDB</a>. This will install the <code>spacetime</code> command line interface (CLI), which contains all the functionality for interacting with SpacetimeDB.</p>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="install-net-8">Install .NET 8<a href="#install-net-8" class="hash-link" aria-label="Direct link to Install .NET 8" title="Direct link to Install .NET 8" translate="no"></a></h2>
|
||
<p>Next we need to <a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0" target="_blank" rel="noopener noreferrer">install .NET 8 SDK</a> so that we can build and publish our module.</p>
|
||
<p>You may already have .NET 8 and can be checked:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">dotnet</span><span style="color:#BD93F9"> --list-sdks</span></span></code></pre>
|
||
<p>.NET 8.0 is the earliest to have the <code>wasi-experimental</code> workload that we rely on, but requires manual activation:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">dotnet</span><span style="color:#F1FA8C"> workload</span><span style="color:#F1FA8C"> install</span><span style="color:#F1FA8C"> wasi-experimental</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="project-structure">Project structure<a href="#project-structure" class="hash-link" aria-label="Direct link to Project structure" title="Direct link to Project structure" translate="no"></a></h2>
|
||
<p>Create and enter a directory <code>quickstart-chat</code>:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">mkdir</span><span style="color:#F1FA8C"> quickstart-chat</span></span>
|
||
<span class="line"><span style="color:#8BE9FD">cd</span><span style="color:#F1FA8C"> quickstart-chat</span></span></code></pre>
|
||
<p>Now create <code>server</code>, our module, which runs in the database:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">spacetime</span><span style="color:#F1FA8C"> init</span><span style="color:#BD93F9"> --lang</span><span style="color:#F1FA8C"> csharp</span><span style="color:#F1FA8C"> server</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="declare-imports">Declare imports<a href="#declare-imports" class="hash-link" aria-label="Direct link to Declare imports" title="Direct link to Declare imports" translate="no"></a></h2>
|
||
<p><code>spacetime init</code> generated a few files:</p>
|
||
<ol>
|
||
<li>Open <code>server/StdbModule.csproj</code> to generate a .sln file for intellisense/validation support.</li>
|
||
<li>Open <code>server/Lib.cs</code>, a trivial module.</li>
|
||
<li>Clear it out, so we can write a new module that's still pretty simple: a bare-bones chat server.</li>
|
||
</ol>
|
||
<p>To start, we'll need to add <code>SpacetimeDB</code> to our using statements. This will give us access to everything we need to author our SpacetimeDB server module.</p>
|
||
<p>To the top of <code>server/Lib.cs</code>, add some imports we'll be using:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#FF79C6">using</span><span style="color:#8BE9FD;font-style:italic"> SpacetimeDB</span><span style="color:#F8F8F2">;</span></span></code></pre>
|
||
<p>We also need to create our static module class which all of the module code will live in. In <code>server/Lib.cs</code>, add:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> partial</span><span style="color:#FF79C6"> class</span><span style="color:#8BE9FD"> Module</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="define-tables">Define tables<a href="#define-tables" class="hash-link" aria-label="Direct link to Define tables" title="Direct link to Define tables" translate="no"></a></h2>
|
||
<p>To get our chat server running, we'll need to store two kinds of data: information about each user, and records of all the messages that have been sent.</p>
|
||
<p>For each <code>User</code>, we'll store their <code>Identity</code>, an optional name they can set to identify themselves to other users, and whether they're online or not. We'll designate the <code>Identity</code> as our primary key, which enforces that it must be unique, indexes it for faster lookup, and allows clients to track updates.</p>
|
||
<p>In <code>server/Lib.cs</code>, add the definition of the table <code>User</code> to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#F8F8F2">[</span><span style="color:#8BE9FD;font-style:italic">Table</span><span style="color:#F8F8F2">(Name </span><span style="color:#FF79C6">=</span><span style="color:#E9F284"> "</span><span style="color:#F1FA8C">user</span><span style="color:#E9F284">"</span><span style="color:#F8F8F2">, Public </span><span style="color:#FF79C6">=</span><span style="color:#BD93F9"> true</span><span style="color:#F8F8F2">)]</span></span>
|
||
<span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> partial</span><span style="color:#FF79C6"> class</span><span style="color:#8BE9FD"> User</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> [</span><span style="color:#8BE9FD;font-style:italic">PrimaryKey</span><span style="color:#F8F8F2">]</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> public</span><span style="color:#8BE9FD;font-style:italic"> Identity</span><span style="color:#F8F8F2"> Identity;</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> public</span><span style="color:#FF79C6"> string</span><span style="color:#F8F8F2">? Name;</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> public</span><span style="color:#FF79C6"> bool</span><span style="color:#F8F8F2"> Online;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<p>For each <code>Message</code>, we'll store the <code>Identity</code> of the user who sent it, the <code>Timestamp</code> when it was sent, and the text of the message.</p>
|
||
<p>In <code>server/Lib.cs</code>, add the definition of the table <code>Message</code> to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#F8F8F2">[</span><span style="color:#8BE9FD;font-style:italic">Table</span><span style="color:#F8F8F2">(Name </span><span style="color:#FF79C6">=</span><span style="color:#E9F284"> "</span><span style="color:#F1FA8C">message</span><span style="color:#E9F284">"</span><span style="color:#F8F8F2">, Public </span><span style="color:#FF79C6">=</span><span style="color:#BD93F9"> true</span><span style="color:#F8F8F2">)]</span></span>
|
||
<span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> partial</span><span style="color:#FF79C6"> class</span><span style="color:#8BE9FD"> Message</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> public</span><span style="color:#8BE9FD;font-style:italic"> Identity</span><span style="color:#F8F8F2"> Sender;</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> public</span><span style="color:#8BE9FD;font-style:italic"> Timestamp</span><span style="color:#F8F8F2"> Sent;</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> public</span><span style="color:#FF79C6"> string</span><span style="color:#F8F8F2"> Text </span><span style="color:#FF79C6">=</span><span style="color:#E9F284"> ""</span><span style="color:#F8F8F2">;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="set-users-names">Set users' names<a href="#set-users-names" class="hash-link" aria-label="Direct link to Set users' names" title="Direct link to Set users' names" translate="no"></a></h2>
|
||
<p>We want to allow users to set their names, because <code>Identity</code> is not a terribly user-friendly identifier. To that effect, we define a reducer <code>SetName</code> which clients can invoke to set their <code>User.Name</code>. It will validate the caller's chosen name, using a function <code>ValidateName</code> which we'll define next, then look up the <code>User</code> record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail.</p>
|
||
<p>Each reducer must accept as its first argument a <code>ReducerContext</code>, which includes contextual data such as the <code>Sender</code> which contains the Identity of the client that called the reducer, and the <code>Timestamp</code> when it was invoked. For now, we only need the <code>Sender</code>.</p>
|
||
<p>It's also possible to call <code>SetName</code> via the SpacetimeDB CLI's <code>spacetime call</code> command without a connection, in which case no <code>User</code> record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a <code>User</code> row for the module owner. You'll have to decide whether the module owner is always online or always offline, though.</p>
|
||
<p>In <code>server/Lib.cs</code>, add to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#F8F8F2">[</span><span style="color:#8BE9FD;font-style:italic">Reducer</span><span style="color:#F8F8F2">]</span></span>
|
||
<span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> void</span><span style="color:#50FA7B"> SetName</span><span style="color:#F8F8F2">(</span><span style="color:#8BE9FD;font-style:italic">ReducerContext</span><span style="color:#FFB86C;font-style:italic"> ctx</span><span style="color:#F8F8F2">, </span><span style="color:#FF79C6">string</span><span style="color:#FFB86C;font-style:italic"> name</span><span style="color:#F8F8F2">)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> name </span><span style="color:#FF79C6">=</span><span style="color:#50FA7B"> ValidateName</span><span style="color:#F8F8F2">(name);</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="color:#FF79C6"> if</span><span style="color:#F8F8F2"> (ctx.Db.user.Identity.</span><span style="color:#50FA7B">Find</span><span style="color:#F8F8F2">(ctx.Sender) </span><span style="color:#FF79C6">is</span><span style="color:#8BE9FD;font-style:italic"> User</span><span style="color:#F8F8F2"> user)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> user.Name </span><span style="color:#FF79C6">=</span><span style="color:#F8F8F2"> name;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> ctx.Db.user.Identity.</span><span style="color:#50FA7B">Update</span><span style="color:#F8F8F2">(user);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<p>For now, we'll just do a bare minimum of validation, rejecting the empty name. You could extend this in various ways, like:</p>
|
||
<ul>
|
||
<li>Comparing against a blacklist for moderation purposes.</li>
|
||
<li>Unicode-normalizing names.</li>
|
||
<li>Rejecting names that contain non-printable characters, or removing characters or replacing them with a placeholder.</li>
|
||
<li>Rejecting or truncating long names.</li>
|
||
<li>Rejecting duplicate names.</li>
|
||
</ul>
|
||
<p>In <code>server/Lib.cs</code>, add to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#6272A4">/// Takes a name and checks if it's acceptable as a user's name.</span></span>
|
||
<span class="line"><span style="color:#FF79C6">private</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> string</span><span style="color:#50FA7B"> ValidateName</span><span style="color:#F8F8F2">(</span><span style="color:#FF79C6">string</span><span style="color:#FFB86C;font-style:italic"> name</span><span style="color:#F8F8F2">)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> if</span><span style="color:#F8F8F2"> (</span><span style="color:#FF79C6">string</span><span style="color:#F8F8F2">.</span><span style="color:#50FA7B">IsNullOrEmpty</span><span style="color:#F8F8F2">(name))</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> throw</span><span style="color:#FF79C6"> new</span><span style="color:#8BE9FD;font-style:italic"> Exception</span><span style="color:#F8F8F2">(</span><span style="color:#E9F284">"</span><span style="color:#F1FA8C">Names must not be empty</span><span style="color:#E9F284">"</span><span style="color:#F8F8F2">);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> return</span><span style="color:#F8F8F2"> name;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="send-messages">Send messages<a href="#send-messages" class="hash-link" aria-label="Direct link to Send messages" title="Direct link to Send messages" translate="no"></a></h2>
|
||
<p>We define a reducer <code>SendMessage</code>, which clients will call to send messages. It will validate the message's text, then insert a new <code>Message</code> record using <code>Message.Insert</code>, with the <code>Sender</code> identity and <code>Time</code> timestamp taken from the <code>ReducerContext</code>.</p>
|
||
<p>In <code>server/Lib.cs</code>, add to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#F8F8F2">[</span><span style="color:#8BE9FD;font-style:italic">Reducer</span><span style="color:#F8F8F2">]</span></span>
|
||
<span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> void</span><span style="color:#50FA7B"> SendMessage</span><span style="color:#F8F8F2">(</span><span style="color:#8BE9FD;font-style:italic">ReducerContext</span><span style="color:#FFB86C;font-style:italic"> ctx</span><span style="color:#F8F8F2">, </span><span style="color:#FF79C6">string</span><span style="color:#FFB86C;font-style:italic"> text</span><span style="color:#F8F8F2">)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> text </span><span style="color:#FF79C6">=</span><span style="color:#50FA7B"> ValidateMessage</span><span style="color:#F8F8F2">(text);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Log.</span><span style="color:#50FA7B">Info</span><span style="color:#F8F8F2">(text);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> ctx.Db.message.</span><span style="color:#50FA7B">Insert</span><span style="color:#F8F8F2">(</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> new</span><span style="color:#8BE9FD;font-style:italic"> Message</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Sender </span><span style="color:#FF79C6">=</span><span style="color:#F8F8F2"> ctx.Sender,</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Text </span><span style="color:#FF79C6">=</span><span style="color:#F8F8F2"> text,</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Sent </span><span style="color:#FF79C6">=</span><span style="color:#F8F8F2"> ctx.Timestamp,</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> );</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<p>We'll want to validate messages' texts in much the same way we validate users' chosen names. As above, we'll do the bare minimum, rejecting only empty messages.</p>
|
||
<p>In <code>server/Lib.cs</code>, add to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#6272A4">/// Takes a message's text and checks if it's acceptable to send.</span></span>
|
||
<span class="line"><span style="color:#FF79C6">private</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> string</span><span style="color:#50FA7B"> ValidateMessage</span><span style="color:#F8F8F2">(</span><span style="color:#FF79C6">string</span><span style="color:#FFB86C;font-style:italic"> text</span><span style="color:#F8F8F2">)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> if</span><span style="color:#F8F8F2"> (</span><span style="color:#FF79C6">string</span><span style="color:#F8F8F2">.</span><span style="color:#50FA7B">IsNullOrEmpty</span><span style="color:#F8F8F2">(text))</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> throw</span><span style="color:#FF79C6"> new</span><span style="color:#8BE9FD;font-style:italic"> ArgumentException</span><span style="color:#F8F8F2">(</span><span style="color:#E9F284">"</span><span style="color:#F1FA8C">Messages must not be empty</span><span style="color:#E9F284">"</span><span style="color:#F8F8F2">);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> return</span><span style="color:#F8F8F2"> text;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<p>You could extend the validation in <code>ValidateMessage</code> in similar ways to <code>ValidateName</code>, or add additional checks to <code>SendMessage</code>, like:</p>
|
||
<ul>
|
||
<li>Rejecting messages from senders who haven't set their names.</li>
|
||
<li>Rate-limiting users so they can't send new messages too quickly.</li>
|
||
</ul>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="set-users-online-status">Set users' online status<a href="#set-users-online-status" class="hash-link" aria-label="Direct link to Set users' online status" title="Direct link to Set users' online status" translate="no"></a></h2>
|
||
<p>In C# modules, you can register for <code>Connect</code> and <code>Disconnect</code> events by using a special <code>ReducerKind</code>. We'll use the <code>Connect</code> event to create a <code>User</code> record for the client if it doesn't yet exist, and to set its online status.</p>
|
||
<p>We'll use <code>reducerContext.Db.User.Identity.Find</code> to look up a <code>User</code> row for <code>ctx.Sender</code>, if one exists. If we find one, we'll use <code>reducerContext.Db.User.Identity.Update</code> to overwrite it with a row that has <code>Online: true</code>. If not, we'll use <code>User.Insert</code> to insert a new row for our new user. All three of these methods are generated by the <code>[SpacetimeDB.Table]</code> attribute, with rows and behavior based on the row attributes. <code>User.Identity.Find</code> returns a nullable <code>User</code>, because the unique constraint from the <code>[PrimaryKey]</code> attribute means there will be either zero or one matching rows. <code>Insert</code> will throw an exception if the insert violates this constraint; if we want to overwrite a <code>User</code> row, we need to do so explicitly using <code>User.Identity.Update</code>.</p>
|
||
<p>In <code>server/Lib.cs</code>, add the definition of the connect reducer to the <code>Module</code> class:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#F8F8F2">[</span><span style="color:#8BE9FD;font-style:italic">Reducer</span><span style="color:#F8F8F2">(ReducerKind.ClientConnected)]</span></span>
|
||
<span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> void</span><span style="color:#50FA7B"> ClientConnected</span><span style="color:#F8F8F2">(</span><span style="color:#8BE9FD;font-style:italic">ReducerContext</span><span style="color:#FFB86C;font-style:italic"> ctx</span><span style="color:#F8F8F2">)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Log.</span><span style="color:#50FA7B">Info</span><span style="color:#F8F8F2">(</span><span style="color:#E9F284">$"</span><span style="color:#F1FA8C">Connect </span><span style="color:#FF79C6">{</span><span style="color:#F8F8F2">ctx</span><span style="color:#F1FA8C">.</span><span style="color:#F8F8F2">Sender</span><span style="color:#FF79C6">}</span><span style="color:#E9F284">"</span><span style="color:#F8F8F2">);</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="color:#FF79C6"> if</span><span style="color:#F8F8F2"> (ctx.Db.user.Identity.</span><span style="color:#50FA7B">Find</span><span style="color:#F8F8F2">(ctx.Sender) </span><span style="color:#FF79C6">is</span><span style="color:#8BE9FD;font-style:italic"> User</span><span style="color:#F8F8F2"> user)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#6272A4"> // If this is a returning user, i.e., we already have a `User` with this `Identity`,</span></span>
|
||
<span class="line"><span style="color:#6272A4"> // set `Online: true`, but leave `Name` and `Identity` unchanged.</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> user.Online </span><span style="color:#FF79C6">=</span><span style="color:#BD93F9"> true</span><span style="color:#F8F8F2">;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> ctx.Db.user.Identity.</span><span style="color:#50FA7B">Update</span><span style="color:#F8F8F2">(user);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> else</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#6272A4"> // If this is a new user, create a `User` object for the `Identity`,</span></span>
|
||
<span class="line"><span style="color:#6272A4"> // which is online, but hasn't set a name.</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> ctx.Db.user.</span><span style="color:#50FA7B">Insert</span><span style="color:#F8F8F2">(</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> new</span><span style="color:#8BE9FD;font-style:italic"> User</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Name </span><span style="color:#FF79C6">=</span><span style="color:#BD93F9"> null</span><span style="color:#F8F8F2">,</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Identity </span><span style="color:#FF79C6">=</span><span style="color:#F8F8F2"> ctx.Sender,</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Online </span><span style="color:#FF79C6">=</span><span style="color:#BD93F9"> true</span><span style="color:#F8F8F2">,</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> );</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<p>Similarly, whenever a client disconnects, the database will execute the <code>OnDisconnect</code> event if it's registered with <code>ReducerKind.ClientDisconnected</code>. We'll use it to un-set the <code>Online</code> status of the <code>User</code> for the disconnected client.</p>
|
||
<p>Add the following code after the <code>OnConnect</code> handler:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#F8F8F2">[</span><span style="color:#8BE9FD;font-style:italic">Reducer</span><span style="color:#F8F8F2">(ReducerKind.ClientDisconnected)]</span></span>
|
||
<span class="line"><span style="color:#FF79C6">public</span><span style="color:#FF79C6"> static</span><span style="color:#FF79C6"> void</span><span style="color:#50FA7B"> ClientDisconnected</span><span style="color:#F8F8F2">(</span><span style="color:#8BE9FD;font-style:italic">ReducerContext</span><span style="color:#FFB86C;font-style:italic"> ctx</span><span style="color:#F8F8F2">)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">{</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> if</span><span style="color:#F8F8F2"> (ctx.Db.user.Identity.</span><span style="color:#50FA7B">Find</span><span style="color:#F8F8F2">(ctx.Sender) </span><span style="color:#FF79C6">is</span><span style="color:#8BE9FD;font-style:italic"> User</span><span style="color:#F8F8F2"> user)</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#6272A4"> // This user should exist, so set `Online: false`.</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> user.Online </span><span style="color:#FF79C6">=</span><span style="color:#BD93F9"> false</span><span style="color:#F8F8F2">;</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> ctx.Db.user.Identity.</span><span style="color:#50FA7B">Update</span><span style="color:#F8F8F2">(user);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#FF79C6"> else</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> {</span></span>
|
||
<span class="line"><span style="color:#6272A4"> // User does not exist, log warning</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> Log.</span><span style="color:#50FA7B">Warn</span><span style="color:#F8F8F2">(</span><span style="color:#E9F284">"</span><span style="color:#F1FA8C">Warning: No user found for disconnected client.</span><span style="color:#E9F284">"</span><span style="color:#F8F8F2">);</span></span>
|
||
<span class="line"><span style="color:#F8F8F2"> }</span></span>
|
||
<span class="line"><span style="color:#F8F8F2">}</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="start-the-server">Start the Server<a href="#start-the-server" class="hash-link" aria-label="Direct link to Start the Server" title="Direct link to Start the Server" translate="no"></a></h2>
|
||
<p>If you haven't already started the SpacetimeDB server, run the <code>spacetime start</code> command in a <em>separate</em> terminal and leave it running while you continue following along.</p>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="publish-the-module">Publish the module<a href="#publish-the-module" class="hash-link" aria-label="Direct link to Publish the module" title="Direct link to Publish the module" translate="no"></a></h2>
|
||
<p>And that's all of our module code! We'll run <code>spacetime publish</code> to compile our module and publish it on SpacetimeDB. <code>spacetime publish</code> takes an optional name which will map to the database's unique address. Clients can connect either by name or by address, but names are much more pleasant. In this example, we'll be using <code>quickstart-chat</code>. Feel free to come up with a unique name, and in the CLI commands, replace where we've written <code>quickstart-chat</code> with the name you chose.</p>
|
||
<p>From the <code>quickstart-chat</code> directory, run:</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">spacetime</span><span style="color:#F1FA8C"> publish</span><span style="color:#BD93F9"> --project-path</span><span style="color:#F1FA8C"> server</span><span style="color:#F1FA8C"> quickstart-chat</span></span></code></pre>
|
||
<p>Note: If the WebAssembly optimizer <code>wasm-opt</code> is installed, <code>spacetime publish</code> will automatically optimize the Web Assembly output of the published module. Instruction for installing the <code>wasm-opt</code> binary can be found in <a href="https://docs.rs/wasm-opt/latest/wasm_opt/" target="_blank" rel="noopener noreferrer">Rust's wasm-opt documentation</a>.</p>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="call-reducers">Call Reducers<a href="#call-reducers" class="hash-link" aria-label="Direct link to Call Reducers" title="Direct link to Call Reducers" translate="no"></a></h2>
|
||
<p>You can use the CLI (command line interface) to run reducers. The arguments to the reducer are passed in JSON format.</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">spacetime</span><span style="color:#F1FA8C"> call</span><span style="color:#F1FA8C"> quickstart-chat</span><span style="color:#F1FA8C"> SendMessage</span><span style="color:#E9F284"> "</span><span style="color:#F1FA8C">Hello, World!</span><span style="color:#E9F284">"</span></span></code></pre>
|
||
<p>Once we've called our <code>SendMessage</code> reducer, we can check to make sure it ran by running the <code>logs</code> command.</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">spacetime</span><span style="color:#F1FA8C"> logs</span><span style="color:#F1FA8C"> quickstart-chat</span></span></code></pre>
|
||
<p>You should now see the output that your module printed in the database.</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">info:</span><span style="color:#F1FA8C"> Hello,</span><span style="color:#F1FA8C"> World!</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="sql-queries">SQL Queries<a href="#sql-queries" class="hash-link" aria-label="Direct link to SQL Queries" title="Direct link to SQL Queries" translate="no"></a></h2>
|
||
<p>SpacetimeDB supports a subset of the SQL syntax so that you can easily query the data of your database. We can run a query using the <code>sql</code> command.</p>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B">spacetime</span><span style="color:#F1FA8C"> sql</span><span style="color:#F1FA8C"> quickstart-chat</span><span style="color:#E9F284"> "</span><span style="color:#F1FA8C">SELECT * FROM message</span><span style="color:#E9F284">"</span></span></code></pre>
|
||
<pre tabindex="0" class="codeBlockStandalone_pMzE thin-scrollbar codeBlockContainer_HZVP theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><code class="codeBlockLines_ckKi"><span class="line"><span style="color:#50FA7B"> sender</span><span style="color:#FF79C6"> |</span><span style="color:#50FA7B"> sent</span><span style="color:#FF79C6"> |</span><span style="color:#50FA7B"> text</span></span>
|
||
<span class="line"><span style="color:#50FA7B">--------------------------------------------------------------------+----------------------------------+-----------------</span></span>
|
||
<span class="line"><span style="color:#50FA7B"> 0x93dda09db9a56d8fa6c024d843e805d8262191db3b4ba84c5efcd1ad451fed4e</span><span style="color:#FF79C6"> |</span><span style="color:#50FA7B"> 2025-04-08T15:47:46.935402+00:00</span><span style="color:#FF79C6"> |</span><span style="color:#50FA7B"> "Hello, world!"</span></span></code></pre>
|
||
<h2 class="anchor anchorWithStickyNavbar_wKCU" id="whats-next">What's next?<a href="#whats-next" class="hash-link" aria-label="Direct link to What's next?" title="Direct link to What's next?" translate="no"></a></h2>
|
||
<p>You've just set up your first database in SpacetimeDB! You can find the full code for this client <a href="https://github.com/clockworklabs/SpacetimeDB/tree/master/sdks/csharp/examples~/quickstart-chat/server" target="_blank" rel="noopener noreferrer">in the C# server module example</a>.</p>
|
||
<p>The next step would be to create a client that interacts with this module. You can use any of SpacetimeDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: <a href="/sdks/rust/quickstart">Rust</a>, <a href="/sdks/c-sharp/quickstart">C#</a>, or <a href="/sdks/typescript/quickstart">TypeScript</a>.</p>
|
||
<p>If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the <a href="/unity/part-1">Unity Comprehensive Tutorial</a>.</p></div></article><nav class="docusaurus-mt-lg pagination-nav" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/modules/rust"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Rust Reference</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/modules/c-sharp"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">C# Reference</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_Ea_L thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#install-spacetimedb" class="table-of-contents__link toc-highlight">Install SpacetimeDB</a></li><li><a href="#install-net-8" class="table-of-contents__link toc-highlight">Install .NET 8</a></li><li><a href="#project-structure" class="table-of-contents__link toc-highlight">Project structure</a></li><li><a href="#declare-imports" class="table-of-contents__link toc-highlight">Declare imports</a></li><li><a href="#define-tables" class="table-of-contents__link toc-highlight">Define tables</a></li><li><a href="#set-users-names" class="table-of-contents__link toc-highlight">Set users' names</a></li><li><a href="#send-messages" class="table-of-contents__link toc-highlight">Send messages</a></li><li><a href="#set-users-online-status" class="table-of-contents__link toc-highlight">Set users' online status</a></li><li><a href="#start-the-server" class="table-of-contents__link toc-highlight">Start the Server</a></li><li><a href="#publish-the-module" class="table-of-contents__link toc-highlight">Publish the module</a></li><li><a href="#call-reducers" class="table-of-contents__link toc-highlight">Call Reducers</a></li><li><a href="#sql-queries" class="table-of-contents__link toc-highlight">SQL Queries</a></li><li><a href="#whats-next" class="table-of-contents__link toc-highlight">What's next?</a></li></ul></div></div></div></div></main></div></div></div><footer class="theme-layout-footer footer"><div class="container container-fluid"></div></footer></div>
|
||
</body>
|
||
</html> |