Quickstart
Hosted y-sweet
The quickest way to try out y-sweet is to get a free key for our hosted version. The hosted version is currently a tech preview – it’s meant for testing and prototyping until we release our complete management platform in September 2023.
Get a quickstart connection string
Use the form below to request a quickstart connection string. It will be sent to your email address, and we’ll let you know when the rest of the management platform is ready.
Clone the examples
In a terminal, use git
to clone the y-sweet repo and change into the examples
directory, and then install the dependencies:
git clone https://github.com/drifting-in-space/y-sweet.git
cd y-sweet/examples
npm install
Run the examples
The examples are configured to pick up CONNECTION_STRING
from the environment and use it to connect to the y-sweet server. This is a string that is emailed to you when you sign up for a preview key (if you are running a local server, it is printed when you start the server, unless you pass --prod
).
CONNECTION_STRING=yss://[...]@y-sweet.net/project/[...] npm run dev
Then, open http://localhost:3000 (opens in a new tab) in your browser and select the color grid example.
By default, the y-sweet server runs on port :8080 and the examples run on :3000.
If everything worked, when you open the example, you should see a string like ?doc=mcZp5-Grxqt5WLMYUj3dI
automatically get appended to the URL. This is a document ID generated by y-sweet. If you open the same URL in another browser window, you should find that state is synchronized between them.
Understanding the code
High-level overview
y-sweet allows clients (usually an application running in the browser) to synchronize a document with other clients and with persistent storage.
Each document in y-sweet corresponds to one shared Yjs Y.Doc
(opens in a new tab) data structure, and is identified by a unique string.
Before a the client can connect to a document, two things must happen:
- The document needs to exist on the server. Your server will use the y-sweet API to create documents.
For the y-sweet demos, we automatically create a doc every time the user opens a demo, unless we find a document ID in the URL. This is good for ephemeral apps, like shared whiteboards. Alternatively, your app could have an explicit “new document” button that invokes the API.
- The client needs a client access token for the document. Your server will use the y-sweet API to generate an access token.
In the demos, the server automatically generates a token for any user who knows the document ID. If your app has user accounts, you could instead store a mapping of which user has access to which document, and query it before generating a client token.
These operations comprise the document management API in y-sweet. Typically, your server will invoke these APIs on behalf of the client after checking that the client's credentials authorize the operation.
Server component
Here's the Next.js (opens in a new tab) page that renders the color grid demo (which can be found in app/color-grid/page.tsx (opens in a new tab)):
import { YDocProvider } from '@y-sweet/react'
import { getOrCreateDoc } from '@y-sweet/sdk'
import { ColorGrid } from './ColorGrid'
import { CONNECTION_STRING } from '@/lib/config'
type HomeProps = {
searchParams: Record<string, string>
}
export default async function Home({ searchParams }: HomeProps) {
const clientToken = await getOrCreateDoc(searchParams.doc, CONNECTION_STRING)
return (
<YDocProvider clientToken={clientToken} setQueryParam="doc">
<ColorGrid />
</YDocProvider>
)
}
searchParams
is passed in by the Next.js server based on parameters in the URL. If the URL is http://my.site/page?doc=abc123
, searchParams
will be { doc: 'abc123' }
.
getOrCreateDoc
is a helper function that wraps createDoc
and getClientToken
. Its behavior depends on whether its first argument is a string
or undefined
, equivalent to this logic (awaits omitted for clarity):
function getOrCreateDoc(docId?: string): ClientToken {
if (docId === undefined) {
let room = createDoc(options)
return getClientToken(room.doc, {}, options)
} else {
return getClientToken(docId, {}, options)
}
}
<YDocProvider>
is a provider that runs on the client (i.e. in the user’s browser). It connects to a y-sweet server based on information passed down to it via clientToken
, which contains both the URL of the server and a token for connecting to it.
<YDocProvider>
makes the Y.Doc
instance available to its children via React context, either directly through useYDoc
, or indirectly through hooks like useMap
and useArray
.
setQueryParam
is an optional parameter that tells the provider to put the document ID in the URL as the "doc"
parameter. The specific name "doc"
is not special to y-sweet, but it should refer to the same parameter pulled from searchParams
.
You don’t need to pass setQueryParam
. If you don’t, the provider will not modify the URL. This is useful if you want more control over the URL scheme, for example if you want the url to be docs/{docId}
instead of docs?doc={docId}
. You can also leave the docId
out of the URL entirely, provided you have another way of looking up the document ID.
Client component
Here’s a simplified version of the <ColorGrid />
component. I’ve omitted some features like color selection to keep it short, but you can find the full code in src/app/color-grid/ColorGrid.tsx (opens in a new tab).
'use client'
import { useMap } from '@y-sweet/react'
export function ColorGrid() {
const items = useMap<boolean>('colorgrid')
return (
<table>
<tbody>
{Array.from({ length: 10 }, (_, i) => (
<tr key={i}>
{Array.from({ length: 10 }, (_, j) => {
const key = `${i},${j}`
const item = items!.get(key)
return (
<td key={key}>
<div
style={{ width: 30, height: 30, backgroundColor: item ? 'black' : 'white' }}
onClick={() => {
items!.set(key, !item)
}}
></div>
</td>
)
})}
</tr>
))}
</tbody>
</table>
)
}
The Y.Doc
constructed in the last section by <YDocProvider />
is passed into the component implicitly via a React context.
You can think of a Y.Doc
as a key/value store that maps keys to shared data structures like maps, text, and arrays. A new Y.Doc
starts out empty with no values bound to it. When we access a field like colorgrid
, we need to specify the type we expect, so that Yjs knows what data structure to return if the field has not been created on the document.
Another reason for specifying the type is that a Y.Doc
doesn’t actually store type information about document-level data structures directly. For example, a Y.Text
is internally stored as a Y.Array
of strings. It’s only when we access the Y.Array
as a Y.Text
that it knows to treat it as one long string.
Note that this is only true of types that are attached directly to the document at the top level. Types can also be nested in other Yjs types, in which case they do store type information, which is why items.get
above does not need to specify a type.
In the color grid example, useMap
extracts the top-level data structure called colorgrid
from the document as a Y.Map
instance. The y-sweet hooks also include hooks like useArray
and useText
corresponding to other Yjs data structures.
Once we have items
, we render a 10x10 grid. Rather than storing the grid data as a 2D array, we store it as a map from cell coordinates to booleans. This is important so that the initial empty data structure represents a valid state (we treat a missing value as false
).
When a cell is clicked, we toggle the value in the map by calling items.set
. This one operation will synchronize the data everywhere:
- The
ColorGrid
React component will re-render immediately, reflecting the change. - The change will be sent to the server, which will broadcast it to all other clients. Those clients will then also re-render.
- The change will be saved to the datastore that backs the y-sweet server (such as an S3 bucket) as part of its next periodic checkpoint.
Next steps
- Explore our demo code (opens in a new tab) and play around with them.
- Read the React guide to learn more about the y-sweet React hooks and JavaScript SDK.