Skip to content

Latest commit

 

History

History
349 lines (230 loc) · 25 KB

STYLE.md

File metadata and controls

349 lines (230 loc) · 25 KB

SpacetimeDB Documentation Style Guide

Purpose of this document

This document describes how the documentation in this repo, which winds up on the SpacetimeDB website, should be written. Much of the content in this repository currently does not meet these standards. Reworking everything to meet these standards is a significant undertaking, and in all honesty will probably never be complete, but at the very least we want to avoid generating new text which doesn't meet our standards. We will request changes on or reject docs PRs which do not obey these rules, even if they are updating or replacing existing docs which also did not obey these rules.

General guidelines

Target audience

The SpacetimeDB documentation should be digestable and clear for someone who is a competent web or game developer, but does not have a strong grounding in theoretical math or CS. This means we generally want to steer clear of overly terse formal notations, instead using natural language (like, English words) to describe what's going on.

The exception: internals docs

We offer some level of leeway on this for documentation of internal, low-level or advanced interfaces. For example, we don't expect the average user to ever need to know the details of the BSATN binary encoding, so we can make some stronger assumptions about the technical background of readers in that context.

On the other hand, this means that docs for these low-level interfaces should be up-front that they're not for everyone. Start each page with something like, "SUBJECT is a low-level implementation detail of HIGHER-LEVEL SYSTEM. Users of HIGHER-LEVEL SYSTEM should not need to worry about SUBJECT. This document is provided for advanced users and those curious about SpacetimeDB internals." Also make the "HIGHER-LEVEL SYSTEM" a link to the documentation for the user-facing component.

Code formatting

Use triple-backtick code blocks for any example longer than half a line on a 100-character-wide terminal. Always include a relevant language for syntax highlighting; reasonable choices are:

  • csharp.
  • rust.
  • typescript.
  • sql.

Use single-backtick inline code highlighting for names of variables, functions, methods &c. Where possible, make these links, usually sharpsign anchor links, to the section of documentation which describes that variable.

In normal text, use italics without any backticks for meta-variables which the user is expected to fill in. Always include an anchor, sentence or "where" clause which describes the meaning of the meta-variable. (E.g. is it a table name? A reducer? An arbitrary string the user can choose? The output of some previous command?)

For meta-variables in code blocks, enclose the meta-variable name in {} curly braces. Use the same meta-variable names in code as in normal text. Always include a sentence or "where" clause which describes the meaning of the meta-variable.

Do not use single-backtick code highlighting for words which are not variable, function, method or type names. (Or other sorts of defined symbols that appear in actual code.) Similarly, do not use italics for words which are not meta-variables that the reader is expected to substitute. In particular, do not use code highlighting for emphasis or to introduce vocabulary.

For example:

To find rows in a table table with a given value in a #[unique] or #[primary_key] column, do:

ctx.db.{table}().{column}().find({value})

where column is the name of the unique column and value is the value you're looking for in that column. This is equivalent to:

SELECT * FROM {table} WHERE {column} = {value}

Pseudocode

Avoid writing pseudocode whenever possible; just write actual code in one of our supported languages. If the file you're writing in is relevant to a specific supported language, use that. If the file applies to the system as a whole, write it in as many of our supported languages as you're comfortable, then ping another team member to help with the languages you don't know.

If it's just for instructional purposes, it can be high-level and include calls to made-up functions, so long as those functions have descriptive names. If you do this, include a note before the code block which clarifies that it's not intended to be runnable as-is.

Describing limitations and future plans

Call missing features "current limitations" and bugs "known issues."

Be up-front about what isn't implemented right now. It's better for our users to be told up front that something is broken or not done yet than for them to expect it to work and to be surprised when it doesn't.

Don't make promises, even weak ones, about what we plan to do in the future, within tutorials or reference documents. Statements about the future belong in a separate "roadmap" or "future plans" document. Our idea of "soon" is often very different from our users', and our priorities shift rapidly and frequently enough that statements about our future plans rarely end up being accurate.

If your document needs to describe a feature that isn't implemented yet, either rewrite to not depend on that feature, or just say that it's a "current limitation" without elaborating further. Include a workaround if there is one.

Reference pages

Reference pages are where intermediate users will look to get a view of all of the capabilities of a tool, and where experienced users will check for specific information on behaviors of the types, functions, methods &c they're using. Each user-facing component in the SpacetimeDB ecosystem should have a reference page.

Each reference page should start with an introduction paragraph that says what the component is and when and how the user will interact with it. It should then either include a section describing how to install or set up that component, or a link to another page which accomplishes the same thing.

Tone, tense and voice

Reference pages should be written in relatively formal language that would seem at home in an encyclopedia or a textbook. Or, say, the Microsoft .NET API reference.

Declarative present tense, for behavior of properties, functions and methods

Use the declarative voice when describing how code works or what it does. For example:

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

An ArrayList can support multiple readers concurrently, as long as the collection is not modified. To guarantee the thread safety of the ArrayList, all operations must be done through the wrapper returned by the Synchronized(IList) method.

Usually don't refer to the reader

Use second-person pronouns (i.e. "you") sparingly to draw attention to actions the reader should take to work around bugs or avoid footguns. Often these advisories should be pulled out into note, warning or quote-blocks. For example:

Enumerating through a collection is intrinsically not a thread-safe procedure. Even when a collection is synchronized, other threads can still modify the collection, which causes the enumerator to throw an exception. To guarantee thread safety during enumeration, you can either lock the collection during the entire enumeration or catch the exceptions resulting from changes made by other threads.

Usually don't refer to "we" or "us"

Use first-person pronouns sparingly to draw attention to non-technical information like design advice. Always use the first-person plural (i.e. "we" or "us") and never the singular (i.e. "I" or "me"). Often these should be accompanied by marker words like "recommend," "advise," "encourage" or "discourage." For example:

We don't recommend that you use the ArrayList class for new development. Instead, we recommend that you use the generic List<T> class.

Usually Avoid Passive Voice

Use active voice rather than passive voice to avoid ambiguity regarding who is doing the action. Active voice directly attributes actions to the subject, making sentences easier to understand. For example:

  • Passive voice: "The method was invoked."
  • Active voice: "The user invoked the method."

The second example is more straightforward and clarifies who is performing the action. In most cases, prefer using the active voice to maintain a clear and direct explanation of code behavior.

However, passive voice may be appropriate in certain contexts where the actor is either unknown or irrelevant. In these cases, the emphasis is placed on the action or result rather than the subject performing it. For example:

  • "The Dispose method is called automatically when the object is garbage collected."

Tables and links

Each reference page should have one or more two-column tables, where the left column are namespace-qualified names or signatures, and the right column are one-sentence descriptions. Headers are optional. If the table contains multiple different kinds of items (e.g. types and functions), the left column should include the kind as a suffix. For example:

Name Description
Microsoft.CSharp.RuntimeBinder Namespace Provides classes and interfaces that support interoperation between Dynamic Language Runtime and C#.
Microsoft.VisualBasic Namespace Contains types that support the Visual Basic Runtime in Visual Basic.

The names should be code-formatted, and should be links to a page or section for that definition. The short descriptions should be the same as are used at the start of the linked page or section (see below).

Authors are encouraged to write multiple different tables on the same page, with headers between introducing them. E.g. it may be useful to divide classes from interfaces, or to divide names by conceptual purpose. For example:

Classes

| ArrayList | Implements the IList interface using an array whose size is dynamically increased as required. | | BitArray | Manages a compact array of bit values, which are represented as Booleans, where true indicates that the bit is on (1) and false indicates the bit is off (0). |

...

Interfaces

| ICollection | Defines size, enumerators, and synchronization methods for all nongeneric collections. | | IComparer | Exposes a method that compares two objects. |

...

Sections for individual definitions

Header

When writing a section for an individual definition, start with any metadata that users will need to refer to the defined object, like its namespace. Then write a short paragraph, usually just a single sentence, which gives a high-level description of the thing. This sentence should be in the declarative present tense with an active verb. Start with the verb, with the thing being defined as the implied subject. For example:

ArrayList Class [...] Namespace: System.Collections [...] Implements the IList interface using an array whose size is dynamically increased as required.

Next, add a triple-backtick code block that contains just the declaration or signature of the variable, function or method you're describing.

What, specifically, counts as the declaration or signature is somewhat context-dependent. A good general rule is that it's everything in the source code to the left of the equals sign = or curly braces {}. You can edit this to remove implementation details (e.g. superclasses that users aren't supposed to see), or to add information that would be helpful but isn't in the source (e.g. trait bounds on generic parameters of types which aren't required to instantiate the type, but which most methods require, like Eq + Hash for HashMap). For example:

public class ArrayList : ICloneable, System.Collections.IList

If necessary, this should be followed by one or more paragraphs of more in-depth description.

Examples

Next, within a subheader named "Examples," include a code block with examples.

To the extent possible, this code block should be freestanding. If it depends on external definitions that aren't included in the standard library or are not otherwise automatically accessible, add a note so that users know what they need to supply themselves (e.g. that the mod module_bindings; refers to the quickstart-chat module). Do not be afraid to paste the same "header" or "prelude" code (e.g. a table declaration) into a whole bunch of code blocks, but try to avoid making easy-to-miss minor edits to such "header" code.

Add comments to this code block which describe what it does. In particular, if the example prints to the console, show the expected output in a comment. For example:

using System;
using System.Collections;
public class SamplesArrayList  {

   public static void Main()  {

      // Creates and initializes a new ArrayList.
      ArrayList myAL = new ArrayList();
      myAL.Add("Hello");
      myAL.Add("World");
      myAL.Add("!");

      // Displays the properties and values of the ArrayList.
      Console.WriteLine( "myAL" );
      Console.WriteLine( "    Count:    {0}", myAL.Count );
      Console.WriteLine( "    Capacity: {0}", myAL.Capacity );
      Console.Write( "    Values:" );
      PrintValues( myAL );
   }

   public static void PrintValues( IEnumerable myList )  {
      foreach ( Object obj in myList )
         Console.Write( "   {0}", obj );
      Console.WriteLine();
   }
}


/*
This code produces output similar to the following:

myAL
    Count:    3
    Capacity: 4
    Values:   Hello   World   !

*/

Child items

If the described item has any children (e.g. properties and methods of classes, variants of enums), include one or more tables for those children, as described above, followed by subsections for each child item. These subsections follow the same format as for the parent items, with a header, declaration, description, examples and tables of any (grand-)children.

If a documentation page ends up with more than 3 layers of nested items, split it so that each top-level item has its own page.

Grammars and syntax

Reference documents, particularly for SQL or our serialization formats, will sometimes need to specify grammars. Before doing this, be sure you need to, as a grammar specification is scary and confusing to even moderately technical readers. If you're describing data that obeys some other language that readers will be familiar with, write a definition in or suited to that language instead of defining the grammar. For example, when describing a JSON encoding, consider writing a TypeScript-style type instead of a grammar.

If you really do need to describe a grammar, write an EBNF description inside a triple-backticks code block with the ebnf language marker. (I assume that any grammar we need to describe will be context-free.) Start with the "topmost" or "entry" nonterminal, i.e. the syntactic construction that we actually want to parse, and work "downward" towards the terminals. For example, when describing SQL, statement is at the top, and literal and ident are at or near the bottom. You don't have to include trivial rules like those for literals.

Then, write a whole bunch of examples under a subheader "Examples" in another tripple-backtick code block, this one with an appropriate language marker for what you're describing. Include at least one simple example and at least one complicated example. Try to include examples which exercise all of the features your grammar can express.

Overview pages

Landing page type things, usually named index.md.

Tone, tense and voice

Use the same guidelines as for reference pages, except that you can refer to the reader as "you" more often.

Links

Include as many links to more specific docs pages as possible within the text. Sharp-links to anchors/headers within other docs pages are super valuable here!

FAQs

If there's any information you want to impart to users but you're not sure how to shoehorn it into any other page or section, just slap it in an "FAQ" section at the bottom of an overview page.

Each FAQ item should start with a subheader, which is phrased as a question a user would ask.

Answer these questions starting with a declarative or conversational sentence. Refer to the asker as "you," and their project as "your client," "your module" or "your app."

For example:

What's the difference between a subscription query and a one-off query?

Subscription queries are incremental: your client receives updates whenever the database state changes, containing only the altered rows. This is an efficient way to maintain a "materialized view," that is, a local copy of some subset of the database. Use subscriptions when you want to watch rows and react to changes, or to keep local copies of rows which you'll read frequently.

A one-off query happens once, and then is done. Use one-off queries to look at rows you only need once.

How do I get an authorization token?

You can supply your users with authorization tokens in several different ways; which one is best for you will depend on the needs of your app. [...] (I don't actually want to write a real answer to this question - pgoldman 2024-11-19.)

Can my client connect to multiple modules at the same time?

Yes! Your client can construct as many DbConnections simultaneously as it wants to, each of which will operate independently. If you want to connect to two modules with different schemas, use spacetime generate to include bindings for both of them in your client project. Note that SpacetimeDB may reject multiple concurrent connections to the same module by a single client.

Tutorial pages

Tutorials are where we funnel new-to-intermediate users to introduce them to new concepts.

Some tutorials are associated with specific SpacetimeDB components, and should be included in (sub)directories alongside the documentation for those components. Other tutorials are more general or holisitc, touching many different parts of SpacetimeDB to produce a complete game or app, and should stand alone or be grouped into a "tutorials" or "projects" directory.

Tone, tense and voice

Be friendly, but still precise and professional. Refer to the reader as "you." Make gentle suggestions for optional actions with "can" or "could." When telling them to do something that's required to advance the tutorial, use the imperative voice. When reminding them of past tutorials or preparing them for future ones, say "we," grouping you (the writer) together with the reader. You two are going on a journey together, so get comfortable!

Scope

You don't have to teach the reader non-SpacetimeDB-specific things. If you're writing a tutorial on Rust modules, for example, assume basic-to-intermediate familiarity with "Rust," so you can focus on teaching the reader about the "modules" part.

Introduction: tell 'em what you're gonna tell 'em

Each tutorial should start with a statement of its scope (what new concepts are introduced), goal (what you build or do during the tutorial) and prerequisites (what other tutorials you should have finished first).

In this tutorial, we'll implement a simple chat server as a SpacetimeDB module. We'll learn how to declare tables and to write reducers, functions which run in the database to modify those tables in response to client requests. Before starting, make sure you've installed SpacetimeDB and logged in with a developer Identity.

Introducing and linking to definitions

The first time a tutorial or series introduces a new type / function / method / &c, include a short paragraph describing what it is and how it's being used in this tutorial. Make sure to link to the reference section on that item.

Tutorial code

If the tutorial involves writing code, e.g. for a module or client, the tutorial should include the complete result code within its text. Ideally, it should be possible for a reader to copy and paste all the code blocks in the document into a file, effectively concatentating them together, and wind up with a coherent and runnable program. Sometimes this is not possible, e.g. because C# requires wrapping your whole file in a bunch of scopes. In this case, precede each code block with a sentence that describes where the reader is going to paste it.

Include even uninteresting code, like imports! You can rush through these without spending too much time on them, but make sure that every line of code required to make the project work appears in the tutorial.

spacetime init should have pre-populated server/src/lib.rs with a trivial module. Clear it out so we can write a new, simple module: a bare-bones chat server.

To the top of server/src/lib.rs, add some imports we'll be using:

use spacetimedb::{table, reducer, Table, ReducerContext, Identity, Timestamp};

For code that is interesting, after the code block, add a description of what the code does. Usually this will be pretty succinct, as the code should hopefully be pretty clear on its own.

Words for telling the user to write code

When introducing a code block that the user should put in their file, don't say "copy" or "paste." Instead, tell them (in the imperative) to "add" or "write" the code. This emphasizes active participation, as opposed to passive consumption, and implicitly encourages the user to modify the tutorial code if they'd like. Readers who just want to copy and paste will do so without our telling them.

To server/src/lib.rs, add the definition of the connect reducer:

I don't actually need to fill this in.

Conclusion

Each tutorial should end with a conclusion section, with a title like "What's next?"

Tell 'em what you told 'em

Start the conclusion with a sentence or paragraph that reminds the reader what they accomplished:

You've just set up your first database in SpacetimeDB, complete with its very own tables and reducers!

Tell them what to do next

If this tutorial is part of a series, link to the next entry:

You can use any of SpacetimDB's supported client languages to do this. Take a look at the quickstart guide for your client language of choice: Rust, C#, or TypeScript. If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the Unity Comprehensive Tutorial or check out our example game, BitcraftMini.

If this tutorial is about a specific component, link to its reference page:

Check out the Rust SDK Reference for a more comprehensive view of the SpacetimeDB Rust SDK.

If this tutorial is the end of a series, or ends with a reasonably complete app, throw in some ideas about how the reader could extend it:

Our basic terminal interface has some limitations. Incoming messages can appear while the user is typing, which is less than ideal. Additionally, the user's input gets mixed with the program's output, making messages the user sends appear twice. You might want to try improving the interface by using Rustyline, Cursive, or even creating a full-fledged GUI.

Once your chat server runs for a while, you might want to limit the messages your client loads by refining your Message subscription query, only subscribing to messages sent within the last half-hour.

You could also add features like:

  • Styling messages by interpreting HTML tags and printing appropriate ANSI escapes.
  • Adding a moderator flag to the User table, allowing moderators to manage users (e.g., time-out, ban).
  • Adding rooms or channels that users can join or leave.
  • Supporting direct messages or displaying user statuses next to their usernames.

Complete code

If the tutorial involved writing code, add a link to the complete code. This should be somewhere on GitHub, either as its own repo, or as an example project within an existing repo. Ensure the linked folder has a README.md file which includes:

  • The name of the tutorial project.
  • How to run or interact with the tutorial project, whatever that means (e.g. publish to testnet and then spacetime call).
  • Links to external dependencies (e.g. for client projects, the module which it runs against).
  • A back-link to the tutorial that builds this project.

At the end of the tutorial that builds the quickstart-chat module in Rust, you might write:

You can find the full code for this module in the SpacetimeDB module examples.