Learn some

Build an issue tracker with distributed state.

Our issues app will be quite simple, but it will have team collaboration. Let's call it... “Circular.”

We'll build everything step-by-step, in typical, immediately usable stages. We'll explore many important things Jazz does — so follow along or just pick things out.

Project Setup

  1. Create a project called "circular" from a generic Vite starter template:

    >
    npx degit gardencmp/vite-ts-react-tailwind circular
    >
    cd circular
    >
    npm install
    >
    npm run dev

    You should now have an empty app running, typically at localhost:5173.

    (If you make changes to the code, the app will automatically refresh.)

  2. Install jazz-tools and jazz-react

    (in a new terminal window):
    >
    cd circular
    >
    npm install jazz-tools jazz-react
  3. Modify src/main.tsx to set up a Jazz context:

    1
    import React from "react";
    2
    import ReactDOM from "react-dom/client";
    3
    import App from "./App.tsx";
    4
    import "./index.css";
    5
    import { createJazzReactContext, DemoAuth } from "jazz-react";
    6
    7
    const Jazz = createJazzReactContext({
    8
    auth: DemoAuth({ appName: "Circular" }),
    9
    peer: "wss://mesh.jazz.tools/?key=you@example.com", // <- put your email here to get a proper API key later
    10
    });
    11
    export const { useAccount, useCoState } = Jazz;
    12
    13
    ReactDOM.createRoot(document.getElementById("root")!).render(
    14
    <Jazz.Provider>
    15
    <React.StrictMode>
    16
    {" "}
    17
    <App />
    18
    </React.StrictMode>{" "}
    19
    </Jazz.Provider>,
    20
    );

    This sets Jazz up, extracts app-specific hooks for later, and wraps our app in the provider.

TODO: explain Auth

Intro to CoValues

Let's learn about the central idea behind Jazz: Collaborative Values.

What if we could treat distributed state like local state? That's what CoValues do.

We can

  • create CoValues, anywhere
  • load CoValues by ID, from anywhere else
  • edit CoValues, from anywhere, by mutating them like local state
  • subscribe to edits in CoValues, whether they're local or remote

Declaring our own CoValues

To make our own CoValues, we first need to declare a schema for them. Think of a schema as a combination of TypeScript types and runtime type information.

Let's start by defining a schema for our most central entity in Circular: an Issue.

Create a new file src/schema.ts and add the following:

1
import { CoMap, co } from "jazz-tools";
2
3
export class Issue extends CoMap {
4
title = co.string;
5
description = co.string;
6
estimate = co.number;
7
status? = co.literal("backlog", "in progress", "done");
8
}

TODO: explain what's happening

Reading from CoValues

CoValues are designed to be read like simple local JSON state. Let's see how we can read from an Issue by building a component to render one.

Create a new file src/components/Issue.tsx and add the following:

1
import { Issue } from "../schema";
2
3
export function IssueComponent({ issue }: { issue: Issue }) {
4
return (
5
<div className="grid grid-cols-6 text-sm border-r border-b [&>*]:p-2 [&>*]:border-l [&>*]:border-t">
6
<h2>{issue.title}</h2>
7
<p className="col-span-3">{issue.description}</p>
8
<p>Estimate: {issue.estimate}</p>
9
<p>Status: {issue.status}</p>
10
</div>
11
);
12
}

Simple enough!

Creating CoValues

To actually see an Issue, we have to create one. This is where things start to get interesting...

Let's modify src/App.tsx to prepare for creating an Issue and then rendering it:

1
import { useState } from "react";
2
import { Issue } from "./schema";
3
import { IssueComponent } from "./components/Issue.tsx";
4
5
function App() {
6
const [issue, setIssue] = useState<Issue>();
7
8
if (issue) {
9
return <IssueComponent issue={issue} />;
10
} else {
11
return <button>Create Issue</button>;
12
}
13
}
14
15
export default App;

Now, finally, let's implement creating an issue:

1
import { useState } from "react";
2
import { Issue } from "./schema";
3
import { IssueComponent } from "./components/Issue.tsx";
4
import { useAccount } from "./main";
5
6
function App() {
7
const { me } = useAccount();
8
const [issue, setIssue] = useState<Issue>();
9
10
const createIssue = () => {
11
const newIssue = Issue.create(
12
{
13
title: "Buy terrarium",
14
description: "Make sure it's big enough for 10 snails.",
15
estimate: 5,
16
status: "backlog",
17
},
18
{ owner: me },
19
);
20
setIssue(newIssue);
21
};
22
23
if (issue) {
24
return <IssueComponent issue={issue} />;
25
} else {
26
return <button onClick={createIssue}>Create Issue</button>;
27
}
28
}
29
30
export default App;

🏁 Now you should be able to create a new issue by clicking the button and then see it rendered!

Preview

Buy terrarium

Make sure it's big enough for 10 snails.

Estimate: 5

Status: backlog

We'll already notice one interesting thing here:

  • We have to create every CoValue with an owner!
    • this will determine access rights on the CoValue, which we'll learn about in "Groups & Permissions"
    • here we set owner to the current user me, which we get from the Jazz context / useAccount

Behind the scenes, Jazz not only creates the Issue in memory but also automatically syncs an encrypted version to the cloud and persists it locally. The Issue also has a globally unique ID.

We'll make use of both of these facts in a bit, but for now let's start with local editing and subscribing.

Editing CoValues and subscribing to edits

Since we're the owner of the CoValue, we should be able to edit it, right?

And since this is a React app, it would be nice to subscribe to edits of the CoValue and reactively re-render the UI, like we can with local state.

This is exactly what the useCoState hook is for!

  • Note that useCoState doesn't take a CoValue directly, but rather a CoValue's schema, plus its ID.
    • So we'll slightly adapt our useState to only keep track of an issue ID...
    • ...and then use useCoState to get the actual issue

Let's modify src/App.tsx:

1
import { useState } from "react";
2
import { Issue } from "./schema";
3
import { IssueComponent } from "./components/Issue.tsx";
4
import { useAccount, useCoState } from "./main";
5
import { ID } from "jazz-tools"
6
7
function App() {
8
const { me } = useAccount();
9
const [issueID, setIssueID] = useState<ID<Issue>>();
10
11
const issue = useCoState(Issue, issueID);
12
13
const createIssue = () => {
14
const newIssue = Issue.create(
15
{
16
title: "Buy terrarium",
17
description: "Make sure it's big enough for 10 snails.",
18
estimate: 5,
19
status: "backlog",
20
},
21
{ owner: me },
22
);
23
setIssueID(newIssue.id);
24
};
25
26
if (issue) {
27
return <IssueComponent issue={issue} />;
28
} else {
29
return <button onClick={createIssue}>Create Issue</button>;
30
}
31
}
32
33
export default App;

And now for the exciting part! Let's make src/components/Issue.tsx an editing component.

1
import { Issue } from "../schema";
2
3
export function IssueComponent({ issue }: { issue: Issue }) {
4
return (
5
<div className="grid grid-cols-6 text-sm border-r border-b [&>*]:p-2 [&>*]:border-l [&>*]:border-t">
6
<input type="text"
7
value={issue.title}
8
onChange={(event) => { issue.title = event.target.value }}/>
9
<textarea className="col-span-3"
10
value={issue.description}
11
onChange={(event) => { issue.description = event.target.value }}/>
12
<label className="flex">
13
Estimate:
14
<input type="number" className="text-right min-w-0"
15
value={issue.estimate}
16
onChange={(event) => { issue.estimate = Number(event.target.value) }}/>
17
</label>
18
<select
19
value={issue.status}
20
onChange={(event) => {
21
issue.status = event.target.value as "backlog" | "in progress" | "done"
22
}}
23
>
24
<option value="backlog">Backlog</option>
25
<option value="in progress">In Progress</option>
26
<option value="done">Done</option>
27
</select>
28
</div>
29
);
30
}

Preview

🏁 Now you should be able to edit the issue after creating it!

You'll immediately notice that we're doing something non-idiomatic for React: we mutate the issue directly, by assigning to its properties.

This works because CoValues

  • intercept these edits
  • update their local view accordingly (React doesn't really care after rendering)
  • notify subscribers of the change (who will receive a fresh, updated view of the CoValue)

We have one subscriber on our Issue, with useCoState in src/App.tsx, which will cause the App component and its children to re-render whenever the Issue changes.

Automatic local & cloud persistence

So far our Issue CoValues just looked like ephemeral local state. We'll now start exploring the first main feature that makes CoValues special: automatic persistence.

Actually, all the Issue CoValues we've created so far have already been automatically persisted to the cloud and locally - but we loose track of their ID after a reload.

So let's store the ID in window URL state and make sure our useState is in sync with that.

1
import { useState } from "react";
2
import { Issue } from "./schema";
3
import { IssueComponent } from "./components/Issue.tsx";
4
import { useAccount, useCoState } from "./main";
5
import { ID } from "jazz-tools"
6
7
function App() {
8
const { me } = useAccount();
9
const [issueID, setIssueID] = useState<ID<Issue> | undefined>(
10
(window.location.search?.replace("?issue=", "") || undefined) as ID<Issue> | undefined,
11
);
12
13
const issue = useCoState(Issue, issueID);
14
15
const createIssue = () => {
16
const newIssue = Issue.create(
17
{
18
title: "Buy terrarium",
19
description: "Make sure it's big enough for 10 snails.",
20
estimate: 5,
21
status: "backlog",
22
},
23
{ owner: me },
24
);
25
setIssueID(newIssue.id);
26
window.history.pushState({}, "", `?issue=${newIssue.id}`);
27
};
28
29
if (issue) {
30
return <IssueComponent issue={issue} />;
31
} else {
32
return <button onClick={createIssue}>Create Issue</button>;
33
}
34
}
35
36
export default App;

🏁 Now you should be able to create an issue, edit it, reload the page, and still see the same issue.

Remote sync

To see that sync is also already working, try the following:

  • copy the URL to a new tab in the same browser window and see the same issue
  • edit the issue and see the changes reflected in the other tab!

This works because we load the issue as the same account that created it and owns it (remember setting { owner: me }?).

We'll learn more about access control in "Groups & Permissions", but for now let's build a super simple way of sharing an Issue by just making it publicly readable & writable.

All we have to do is create a new group to own each new issue and add "everyone" as a "writer":

1
import { useState } from "react";
2
import { Issue } from "./schema";
3
import { IssueComponent } from "./components/Issue.tsx";
4
import { useAccount, useCoState } from "./main";
5
import { ID, Group } from "jazz-tools"
6
7
function App() {
8
const { me } = useAccount();
9
const [issueID, setIssueID] = useState<ID<Issue> | undefined>(
10
(window.location.search?.replace("?issue=", "") || undefined) as ID<Issue> | undefined,
11
);
12
13
const issue = useCoState(Issue, issueID);
14
15
const createIssue = () => {
16
const group = Group.create({ owner: me });
17
group.addMember("everyone", "writer");
18
19
const newIssue = Issue.create(
20
{
21
title: "Buy terrarium",
22
description: "Make sure it's big enough for 10 snails.",
23
estimate: 5,
24
status: "backlog",
25
},
26
{ owner: group },
27
);
28
setIssueID(newIssue.id);
29
window.history.pushState({}, "", `?issue=${newIssue.id}`);
30
};
31
32
if (issue) {
33
return <IssueComponent issue={issue} />;
34
} else {
35
return <button onClick={createIssue}>Create Issue</button>;
36
}
37
}
38
39
export default App;

🏁 Now you should be able to open the Issue (with its unique URL) on another device or browser, or send it to a friend and you should be able to edit it together in realtime!

This concludes our intro to the essence of CoValues. Hopefully you're starting to have a feeling for how CoValues behave and how they're magically available everywhere.

Refs & Auto-Subscribe

Now let's have a look at how to compose CoValues into more complex structures and build a whole app around them.

Let's extend our two data model to include "Projects" which have a list of tasks and some properties of their own.

Using plain objects, you would probably type a Project like this:

1
type Project = {
2
name: string;
3
issues: Issue[];
4
};

In order to create this more complex structure in a fully collaborative way, we're going to need references that allow us to nest or link CoValues.

Add the following to src/schema.ts:

1
import { CoMap, CoList, co } from "jazz-tools";
2
3
export class Issue extends CoMap {
4
title = co.string;
5
description = co.string;
6
estimate = co.number;
7
status? = co.literal("backlog", "in progress", "done");
8
}
9
10
export class ListOfIssues extends CoList.Of(co.ref(Issue)) {}
11
12
export class Project extends CoMap {
13
name = co.string;
14
issues = co.ref(ListOfIssues);
15
}

Now let's change things up a bit in terms of components as well.

First, we'll change App.tsx to create and render Projects instead of Issues. (We'll move the useCoState into the ProjectComponent we'll create in a second).

1
import { useState } from "react";
2
import { Project, ListOfIssues } from "./schema";
3
import { ProjectComponent } from "./components/Project.tsx";
4
import { useAccount } from "./main";
5
import { ID, Group } from "jazz-tools"
6
7
function App() {
8
const { me } = useAccount();
9
const [projectID, setProjectID] = useState<ID<Project> | undefined>(
10
(window.location.search?.replace("?project=", "") || undefined) as ID<Project> | undefined,
11
);
12
const issue = useCoState(Issue, issueID);
13
14
const createProject = () => {
15
const group = Group.create({ owner: me });
16
group.addMember("everyone", "writer");
17
18
const newProject = Project.create(
19
{
20
name: "New Project",
21
issues: ListOfIssues.create([], { owner: group })
22
},
23
{ owner: group },
24
);
25
setProjectID(newProject.id);
26
window.history.pushState({}, "", `?project=${newProject.id}`);
27
};
28
29
if (projectID) {
30
return <ProjectComponent projectID={projectID} />;
31
} else {
32
return <button onClick={createProject}>Create Project</button>;
33
}
34
}
35
36
export default App;

Now we'll actually create the ProjectComponent that renders a Project and its Issues.

Create a new file src/components/Project.tsx and add the following:

1
import { ID } from "jazz-tools";
2
import { Project, Issue } from "../schema";
3
import { IssueComponent } from "./Issue.tsx";
4
import { useCoState } from "../main";
5
6
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
7
const project = useCoState(Project, projectID);
8
9
const createAndAddIssue = () => {
10
project?.issues?.push(Issue.create({
11
title: "",
12
description: "",
13
estimate: 0,
14
status: "backlog",
15
}, { owner: project._owner }));
16
};
17
18
return project ? (
19
<div>
20
<h1>{project.name}</h1>
21
<div className="border-r border-b">
22
{project.issues?.map((issue) => (
23
issue && <IssueComponent key={issue.id} issue={issue} />
24
))}
25
<button onClick={createAndAddIssue}>Create Issue</button>
26
</div>
27
</div>
28
) : (
29
<div>Loading project...</div>
30
);
31
}

🏁 Now you should be able to create a project, add issues to it, share it, and edit it collaboratively!

Two things to note here:

  • We create a new Issue like before, and then push it into the issues list of the Project. By setting the owner to the Project's owner, we ensure that the Issue has the same access rights as the project itself.
  • We only need to use useCoState on the Project, and the nested ListOfIssues and each Issue will be automatically loaded and subscribed to when we access them.
  • However, because either the Project, ListOfIssues, or each Issue might not be loaded yet, we have to check for them being defined.

The load-and-subscribe-on-access is a convenient way to have your rendering drive data loading (including in nested components!) and lets you quickly chuck UIs together without worrying too much about the shape of all data you'll need.

But you can also take more precise control over loading by defining a minimum-depth to load in useCoState:

1
import { ID } from "jazz-tools";
2
import { Project, Issue } from "../schema";
3
import { IssueComponent } from "./Issue.tsx";
4
import { useCoState } from "../main";
5
6
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
7
const project = useCoState(Project, projectID, { issues: [{}] });
8
9
const createAndAddIssue = () => {
10
project?.issues.push(Issue.create({
11
title: "",
12
description: "",
13
estimate: 0,
14
status: "backlog",
15
}, { owner: project._owner }));
16
};
17
18
return project ? (
19
<div>
20
<h1>{project.name}</h1>
21
<div className="border-r border-b">
22
{project.issues.map((issue) => (
23
<IssueComponent key={issue.id} issue={issue} />
24
))}
25
<button onClick={createAndAddIssue}>Create Issue</button>
26
</div>
27
</div>
28
) : (
29
<div>Loading project...</div>
30
);
31
}

The loading-depth spec { issues: [{}] } means "in Project, load issues and load each item in issues shallowly". (Since an Issue doesn't have any further references, "shallowly" actually means all its properties will be available).

  • Now, we can get rid of a lot of coniditional accesses because we know that once project is loaded, project.issues and each Issue in it will be loaded as well.
  • This also results in only one rerender and visual update when everything is loaded, which is faster (especially for long lists) and gives you more control over the loading UX.

TODO: explainer about not loaded vs not set/defined and _refs basics

🚧 OH NO - This is as far as we've written the Guide. 🚧

-> Complain on GitHub

Groups & Permissions

Auth, Accounts & Migrations

Edit Metadata & Time Travel

Backend Workers

FAQ

🚧 OH NO - We don't have any FAQ yet. 🚧 -> Complain on GitHub

API Reference

🚧 OH NO - These docs are still highly work-in-progress. 🚧 -> Complain on GitHub

jazz-tools

CoValues

type ID

type ID<T> = CojsonInternalTypes.RawCoID & IDMarker<T>

⚠️ undocumented

class CoMap

CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
Declaration
Declare your own CoMap schemas by subclassing CoMap and assigning field schemas with co.

Optional
co.ref(...) fields must be marked with { optional: true }.

Example:
import { co, CoMap } from "jazz-tools";

class Person extends CoMap {
name = co.string;
age = co.number;
pet = co.ref(Animal);
car = co.ref(Car, { optional: true });
}
CoMap.Record<Value>(value): RecordLikeCoMap
value: IfCo<Value, Value>
Declare a Record-like CoMap schema, by extending CoMap.Record(...) and passing the value schema using co. Keys are always string.
Example:
import { co, CoMap } from "jazz-tools";

class ColorToFruitMap extends CoMap.Record(
co.ref(Fruit)
) {}

// assume we have map: ColorToFruitMap
// and strawberry: Fruit
map["red"] = strawberry;
Content
You can access properties you declare on a CoMap (using co) as if they were normal properties on a plain object, using dot notation, Object.keys(), etc.

Example:
person.name;
person["age"];
person.age = 42;
person.pet?.name;
Object.keys(person);
// => ["name", "age", "pet"]
.id: ID<CoMap>
The ID of this CoMap
._refs: {[Key in string]: IfCo<this[Key], RefIfCoValue<this[Key]>>}
If property prop is a co.ref(...), you can use coMaps._refs.prop to access
the
Ref instead of the potentially loaded/null value.

This allows you to always get the ID or load the value manually.
Example:
person._refs.pet.id; // => ID<Animal>
person._refs.pet.value;
// => Animal | null
const pet = await person._refs.pet.load();
Creation
CoMap.create<M>(init, options): M
M extends CoMap
this: CoValueClass<M>,
init: Simplify<CoMapInit<M>>,
options: { owner: Group | Account }
Create a new CoMap with the given initial values and owner.

The owner (a Group or Account) determines access rights to the CoMap.

The CoMap will immediately be persisted and synced to connected peers.
Example:
const person = Person.create({
name: "Alice",
age: 42,
pet: cat,
}, { owner: friendGroup });
Subscription & Loading
CoMap.load<M, Depth>(id, as, depth): Promise<DeeplyLoaded<M, Depth> | undefined>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
as: Account,
depth: Depth & DepthsIn<M>
Load a CoMap with a given ID, as a given account.

depth specifies which (if any) fields that reference other CoValues to load as well before resolving.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or {} for shallowly loading only this CoMap, or { fieldA: depthA, fieldB: depthB } for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.
Example:
const person = await Person.load(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ pet: {} }
);
CoMap.loadEf<M, Depth>(id, depth): Effect<DeeplyLoaded<M, Depth>, "unavailable", AccountCtx>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
depth: Depth & DepthsIn<M>
Effectful version of CoMap.load().

Needs to be run inside an
AccountCtx context.
CoMap.subscribe<M, Depth>(id, as, depth, listener): () => void
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
as: Account,
depth: Depth & DepthsIn<M>,
listener: (value: DeeplyLoaded<M, Depth>) => void
Load and subscribe to a CoMap with a given ID, as a given account.

Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.

depth specifies which (if any) fields that reference other CoValues to load as well before calling listener for the first time.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or {} for shallowly loading only this CoMap, or { fieldA: depthA, fieldB: depthB } for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.

Returns an unsubscribe function that you should call when you no longer need updates.

Also see the
useCoState hook to reactively subscribe to a CoValue in a React component.
Example:
const unsub = Person.subscribe(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ pet: {} },
(person) => console.log(person)
);
CoMap.subscribeEf<M, Depth>(id, depth): Stream<DeeplyLoaded<M, Depth>, "unavailable", AccountCtx>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
depth: Depth & DepthsIn<M>
Effectful version of CoMap.subscribe() that returns a stream of updates.

Needs to be run inside an
AccountCtx context.
.ensureLoaded<M, Depth>(depth): Promise<DeeplyLoaded<M, Depth> | undefined>
M extends CoMap
this: M,
depth: Depth & DepthsIn<M>
Given an already loaded CoMap, ensure that the specified fields are loaded to the specified depth.

Works like
CoMap.load(), but you don't need to pass the ID or the account to load as again.
.subscribe<M, Depth>(depth, listener): () => void
M extends CoMap
this: M,
depth: Depth & DepthsIn<M>,
listener: (value: DeeplyLoaded<M, Depth>) => void
Given an already loaded CoMap, subscribe to updates to the CoMap and ensure that the specified fields are loaded to the specified depth.

Works like
CoMap.subscribe(), but you don't need to pass the ID or the account to load as again.

Returns an unsubscribe function that you should call when you no longer need updates.
Collaboration
._edits: {[Key in string]: IfCo<this[Key], CoMapEdit<this[Key]>>}
⚠️ undocumented
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any
⚠️ undocumented
.[inspect](): any
⚠️ undocumented
Internals
CoMap.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawCoMap<{ undefined }, JsonObject | null>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "CoMap"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented

class CoList<Item>

CoLists are collaborative versions of plain arrays.

*
Declaration
CoList.Of<Item>(item): CoList
item: Item
Declare a CoList by subclassing CoList.Of(...) and passing the item schema using co.
Example:
class ColorList extends CoList.Of(
co.string
) {}
class AnimalList extends CoList.Of(
co.ref(Animal)
) {}
Content
You can access items on a CoList as if they were normal items on a plain array, using [] notation, etc.

Since
CoList is a subclass of Array, you can use all the normal array methods like push, pop, splice, etc.

Example:
colorList[0];
colorList[3] = "yellow";
colorList.push("Kawazaki Green");
colorList.splice(1, 1);
.id: ID<CoList<Item>>
The ID of this CoList
._refs: { undefined } & { length: number, [iterator]: NO TYPE }
If a CoList's items are a co.ref(...), you can use coList._refs[i] to access
the
Ref instead of the potentially loaded/null value.

This allows you to always get the ID or load the value manually.
Example:
animals._refs[0].id; // => ID<Animal>
animals._refs[0].value;
// => Animal | null
const animal = await animals._refs[0].load();
Creation
CoList.create<L>(items, options): L
L extends CoList<any>
this: CoValueClass<L>,
items: UnCo<L[number]>[],
options: { owner: Group | Account }
Create a new CoList with the given initial values and owner.

The owner (a Group or Account) determines access rights to the CoMap.

The CoList will immediately be persisted and synced to connected peers.
Example:
const colours = ColorList.create(
["red", "green", "blue"],
{ owner: me }
);
const animals = AnimalList.create(
[cat, dog, fish],
{ owner: me }
);
Subscription & Loading
CoList.load<L, Depth>(id, as, depth): Promise<DeeplyLoaded<L, Depth> | undefined>
L extends CoList<any>
this: CoValueClass<L>,
id: ID<L>,
as: Account,
depth: Depth & DepthsIn<L>
Load a CoList with a given ID, as a given account.

depth specifies if item CoValue references should be loaded as well before resolving.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or for shallowly loading only this CoList, or [itemDepth] for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.
Example:
const animalsWithVets =
await ListOfAnimals.load(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
[{ vet: {} }]
);
CoList.loadEf<L, Depth>(id, depth): Effect<DeeplyLoaded<L, Depth>, "unavailable", AccountCtx>
L extends CoList<any>
this: CoValueClass<L>,
id: ID<L>,
depth: Depth & DepthsIn<L>
Effectful version of CoList.load().

Needs to be run inside an
AccountCtx context.
CoList.subscribe<L, Depth>(id, as, depth, listener): () => void
L extends CoList<any>
this: CoValueClass<L>,
id: ID<L>,
as: Account,
depth: Depth & DepthsIn<L>,
listener: (value: DeeplyLoaded<L, Depth>) => void
Load and subscribe to a CoList with a given ID, as a given account.

Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.

depth specifies if item CoValue references should be loaded as well before calling listener for the first time.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or for shallowly loading only this CoList, or [itemDepth] for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.

Returns an unsubscribe function that you should call when you no longer need updates.

Also see the
useCoState hook to reactively subscribe to a CoValue in a React component.
Example:
const unsub = ListOfAnimals.subscribe(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ vet: {} },
(animalsWithVets) => console.log(animalsWithVets)
);
CoList.subscribeEf<L, Depth>(id, depth): Stream<DeeplyLoaded<L, Depth>, "unavailable", AccountCtx>
L extends CoList<any>
this: CoValueClass<L>,
id: ID<L>,
depth: Depth & DepthsIn<L>
Effectful version of CoList.subscribe() that returns a stream of updates.

Needs to be run inside an
AccountCtx context.
.ensureLoaded<L, Depth>(depth): Promise<DeeplyLoaded<L, Depth> | undefined>
L extends CoList<any>
this: L,
depth: Depth & DepthsIn<L>
Given an already loaded CoList, ensure that items are loaded to the specified depth.

Works like
CoList.load(), but you don't need to pass the ID or the account to load as again.
.subscribe<L, Depth>(depth, listener): () => void
L extends CoList<any>
this: L,
depth: Depth & DepthsIn<L>,
listener: (value: DeeplyLoaded<L, Depth>) => void
Given an already loaded CoList, subscribe to updates to the CoList and ensure that items are loaded to the specified depth.

Works like
CoList.subscribe(), but you don't need to pass the ID or the account to load as again.

Returns an unsubscribe function that you should call when you no longer need updates.
Collaboration
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any[]
⚠️ undocumented
.[inspect](): any[]
⚠️ undocumented
Internals
CoList.fromRaw<V>(raw): V & CoList<any>
V extends CoList<any>
this: CoValueClass<V> & CoList,
raw: RawCoList<JsonValue, JsonObject | null>
⚠️ undocumented
._raw: RawCoList<JsonValue, JsonObject | null>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "CoList"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
CoList[species]: ArrayConstructor
⚠️ undocumented
CoList.isArray(arg): arg is any[]
arg: any
⚠️ undocumented
CoList.from<T>(arrayLike): T[]
arrayLike: ArrayLike<T>
Creates an array from an array-like object.
CoList.from<T, U>(arrayLike, mapfn, thisArg): U[]
arrayLike: ArrayLike<T>,
mapfn: (v: T, k: number) => U,
thisArg: any
Creates an array from an iterable object.
CoList.from<T>(iterable): T[]
iterable: ArrayLike<T> | Iterable<T>
Creates an array from an iterable object.
CoList.from<T, U>(iterable, mapfn, thisArg): U[]
iterable: ArrayLike<T> | Iterable<T>,
mapfn: (v: T, k: number) => U,
thisArg: any
Creates an array from an iterable object.
.constructor: NO TYPE
⚠️ undocumented
._edits: { undefined }
⚠️ undocumented
._loadedAs: Account
⚠️ undocumented
.push(items): number
items: Item[]
⚠️ undocumented
.unshift(items): number
items: Item[]
⚠️ undocumented
.pop(): Item | undefined
⚠️ undocumented
.shift(): Item | undefined
⚠️ undocumented
.splice(start, deleteCount, items): Item[]
start: number,
deleteCount: number,
items: Item[]
⚠️ undocumented
.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
.toString(): string
Returns a string representation of an array.
.toLocaleString(): string
Returns a string representation of an array. The elements are converted to string using their toLocaleString methods.
.concat(items): Item[]
items: ConcatArray<Item>[]
Combines two or more arrays.
This method returns a new array without modifying any existing arrays.
.concat(items): Item[]
items: ConcatArray<Item> | Item[]
Combines two or more arrays.
This method returns a new array without modifying any existing arrays.
.join(separator): string
separator: string
Adds all the elements of an array into a string, separated by the specified separator string.
.reverse(): Item[]
Reverses the elements in an array in place.
This method mutates the array and returns a reference to the same array.
.slice(start, end): Item[]
start: number,
end: number
Returns a copy of a section of an array.
For both start and end, a negative index can be used to indicate an offset from the end of the array.
For example, -2 refers to the second to last element of the array.
.sort(compareFn): this
compareFn: (a: Item, b: Item) => number
Sorts an array in place.
This method mutates the array and returns a reference to the same array.
.indexOf(searchElement, fromIndex): number
searchElement: Item,
fromIndex: number
Returns the index of the first occurrence of a value in an array, or -1 if it is not present.
.lastIndexOf(searchElement, fromIndex): number
searchElement: Item,
fromIndex: number
Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present.
.every<S>(predicate, thisArg): this is S[]
predicate: (value: Item, index: number, array: Item[]) => value is S,
thisArg: any
Determines whether all the members of an array satisfy the specified test.
.every(predicate, thisArg): boolean
predicate: (value: Item, index: number, array: Item[]) => unknown,
thisArg: any
Determines whether all the members of an array satisfy the specified test.
.some(predicate, thisArg): boolean
predicate: (value: Item, index: number, array: Item[]) => unknown,
thisArg: any
Determines whether the specified callback function returns true for any element of an array.
.forEach(callbackfn, thisArg): void
callbackfn: (value: Item, index: number, array: Item[]) => void,
thisArg: any
Performs the specified action for each element in an array.
.map<U>(callbackfn, thisArg): U[]
callbackfn: (value: Item, index: number, array: Item[]) => U,
thisArg: any
Calls a defined callback function on each element of an array, and returns an array that contains the results.
.filter<S>(predicate, thisArg): S[]
predicate: (value: Item, index: number, array: Item[]) => value is S,
thisArg: any
Returns the elements of an array that meet the condition specified in a callback function.
.filter(predicate, thisArg): Item[]
predicate: (value: Item, index: number, array: Item[]) => unknown,
thisArg: any
Returns the elements of an array that meet the condition specified in a callback function.
.reduce(callbackfn): Item
callbackfn: (previousValue: Item, currentValue: Item, currentIndex: number, array: Item[]) => Item
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
.reduce(callbackfn, initialValue): Item
callbackfn: (previousValue: Item, currentValue: Item, currentIndex: number, array: Item[]) => Item,
initialValue: Item
⚠️ undocumented
.reduce<U>(callbackfn, initialValue): U
callbackfn: (previousValue: U, currentValue: Item, currentIndex: number, array: Item[]) => U,
initialValue: U
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
.reduceRight(callbackfn): Item
callbackfn: (previousValue: Item, currentValue: Item, currentIndex: number, array: Item[]) => Item
Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
.reduceRight(callbackfn, initialValue): Item
callbackfn: (previousValue: Item, currentValue: Item, currentIndex: number, array: Item[]) => Item,
initialValue: Item
⚠️ undocumented
.reduceRight<U>(callbackfn, initialValue): U
callbackfn: (previousValue: U, currentValue: Item, currentIndex: number, array: Item[]) => U,
initialValue: U
Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
.find<S>(predicate, thisArg): S | undefined
predicate: (value: Item, index: number, obj: Item[]) => value is S,
thisArg: any
Returns the value of the first element in the array where predicate is true, and undefined
otherwise.
.find(predicate, thisArg): Item | undefined
predicate: (value: Item, index: number, obj: Item[]) => unknown,
thisArg: any
⚠️ undocumented
.findIndex(predicate, thisArg): number
predicate: (value: Item, index: number, obj: Item[]) => unknown,
thisArg: any
Returns the index of the first element in the array where predicate is true, and -1
otherwise.
.fill(value, start, end): this
value: Item,
start: number,
end: number
Changes all array elements from start to end index to a static value and returns the modified array
.copyWithin(target, start, end): this
target: number,
start: number,
end: number
Returns the this object after copying a section of the array identified by start and end
to the same array starting at position target
.entries(): IterableIterator<[number, Item]>
Returns an iterable of key, value pairs for every entry in the array
.keys(): IterableIterator<number>
Returns an iterable of keys in the array
.values(): IterableIterator<Item>
Returns an iterable of values in the array
.includes(searchElement, fromIndex): boolean
searchElement: Item,
fromIndex: number
Determines whether an array includes a certain element, returning true or false as appropriate.
.flatMap<U, This>(callback, thisArg): U[]
callback: (this: This, value: Item, index: number, array: Item[]) => TODO type typeOperator | U,
thisArg: This
Calls a defined callback function on each element of an array. Then, flattens the result into
a new array.
This is identical to a map followed by flat with depth 1.
.flat<A, D>(depth): FlatArray<A, D>[]
D extends number
this: A,
depth: D
Returns a new array with all sub-array elements concatenated into it recursively up to the
specified depth.
.at(index): Item | undefined
index: number
Returns the item located at the specified index.
.findLast<S>(predicate, thisArg): S | undefined
predicate: (value: Item, index: number, array: Item[]) => value is S,
thisArg: any
Returns the value of the last element in the array where predicate is true, and undefined
otherwise.
.findLast(predicate, thisArg): Item | undefined
predicate: (value: Item, index: number, array: Item[]) => unknown,
thisArg: any
⚠️ undocumented
.findLastIndex(predicate, thisArg): number
predicate: (value: Item, index: number, array: Item[]) => unknown,
thisArg: any
Returns the index of the last element in the array where predicate is true, and -1
otherwise.
.toReversed(): Item[]
Returns a copy of an array with its elements reversed.
.toSorted(compareFn): Item[]
compareFn: (a: Item, b: Item) => number
Returns a copy of an array with its elements sorted.
.toSpliced(start, deleteCount, items): Item[]
start: number,
deleteCount: number,
items: Item[]
Copies an array and removes elements and, if necessary, inserts new elements in their place. Returns the copied array.
.toSpliced(start, deleteCount): Item[]
start: number,
deleteCount: number
Copies an array and removes elements while returning the remaining elements.
.with(index, value): Item[]
index: number,
value: Item
Copies an array, then overwrites the value at the provided index with the
given value. If the index is negative, then it replaces from the end
of the array.
.[iterator](): IterableIterator<Item>
Iterator
[unscopables]: { length: boolean, toString: NO TYPE, toLocaleString: NO TYPE, pop: NO TYPE, push: NO TYPE, concat: NO TYPE, join: NO TYPE, reverse: NO TYPE, shift: NO TYPE, slice: NO TYPE, sort: NO TYPE, splice: NO TYPE, unshift: NO TYPE, indexOf: NO TYPE, lastIndexOf: NO TYPE, every: NO TYPE, some: NO TYPE, forEach: NO TYPE, map: NO TYPE, filter: NO TYPE, reduce: NO TYPE, reduceRight: NO TYPE, find: NO TYPE, findIndex: NO TYPE, fill: NO TYPE, copyWithin: NO TYPE, entries: NO TYPE, keys: NO TYPE, values: NO TYPE, includes: NO TYPE, flatMap: NO TYPE, flat: NO TYPE, at: NO TYPE, findLast: NO TYPE, findLastIndex: NO TYPE, toReversed: NO TYPE, toSorted: NO TYPE, toSpliced: NO TYPE, with: NO TYPE, [iterator]: NO TYPE, [unscopables]: boolean }
Is an object whose properties have the value 'true'
when they will be absent when used in a 'with' statement.

class CoStream<Item>

Content
.id: ID<CoStream<Item>>
⚠️ undocumented
Subscription & Loading
CoStream.load<S, Depth>(id, as, depth): Promise<DeeplyLoaded<S, Depth> | undefined>
S extends CoStream<any>
this: CoValueClass<S>,
id: ID<S>,
as: Account,
depth: Depth & DepthsIn<S>
⚠️ undocumented
CoStream.loadEf<S, Depth>(id, depth): Effect<DeeplyLoaded<S, Depth>, "unavailable", AccountCtx>
S extends CoStream<any>
this: CoValueClass<S>,
id: ID<S>,
depth: Depth & DepthsIn<S>
⚠️ undocumented
CoStream.subscribe<S, Depth>(id, as, depth, listener): () => void
S extends CoStream<any>
this: CoValueClass<S>,
id: ID<S>,
as: Account,
depth: Depth & DepthsIn<S>,
listener: (value: DeeplyLoaded<S, Depth>) => void
⚠️ undocumented
CoStream.subscribeEf<S, Depth>(id, depth): Stream<DeeplyLoaded<S, Depth>, "unavailable", AccountCtx>
S extends CoStream<any>
this: CoValueClass<S>,
id: ID<S>,
depth: Depth & DepthsIn<S>
⚠️ undocumented
.ensureLoaded<S, Depth>(depth): Promise<DeeplyLoaded<S, Depth> | undefined>
S extends CoStream<any>
this: S,
depth: Depth & DepthsIn<S>
⚠️ undocumented
.subscribe<S, Depth>(depth, listener): () => void
S extends CoStream<any>
this: S,
depth: Depth & DepthsIn<S>,
listener: (value: DeeplyLoaded<S, Depth>) => void
⚠️ undocumented
Collaboration
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): { in: { undefined }, id: ID<CoStream<Item>>, _type: "CoStream" }
⚠️ undocumented
.[inspect](): { in: { undefined }, id: ID<CoStream<Item>>, _type: "CoStream" }
⚠️ undocumented
Internals
CoStream.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawCoStream<JsonValue, JsonObject | null>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "CoStream"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
CoStream.Of<Item>(item): CoStream
item: IfCo<Item, Item>
⚠️ undocumented
CoStream._schema: any
⚠️ undocumented
CoStream.create<S>(init, options): S
S extends CoStream<any>
this: CoValueClass<S>,
init: TODO type conditional,
options: { owner: Group | Account }
⚠️ undocumented
CoStream.schema<V>(def): void
V extends CoStream<any>
this: TODO reflection type 512 & CoStream,
def: { [ItemsSym]: V["_schema"][ItemsSym] }
⚠️ undocumented
.constructor: NO TYPE
⚠️ undocumented
._schema: { [ItemsSym]: SchemaFor<Item> }
⚠️ undocumented
.byMe: CoStreamEntry<Item> | undefined
⚠️ undocumented
.perSession: { undefined }
⚠️ undocumented
.inCurrentSession: CoStreamEntry<Item> | undefined
⚠️ undocumented
.push(items): void
items: Item[]
⚠️ undocumented
.pushItem(item): void
item: Item
⚠️ undocumented
._loadedAs: Account
⚠️ undocumented

class BinaryCoStream

Content
.id: ID<BinaryCoStream>
⚠️ undocumented
Subscription & Loading
BinaryCoStream.load<B, Depth>(id, as, depth): Promise<DeeplyLoaded<B, Depth> | undefined>
B extends BinaryCoStream
this: CoValueClass<B>,
id: ID<B>,
as: Account,
depth: Depth & DepthsIn<B>
⚠️ undocumented
BinaryCoStream.loadEf<B, Depth>(id, depth): Effect<DeeplyLoaded<B, Depth>, "unavailable", AccountCtx>
B extends BinaryCoStream
this: CoValueClass<B>,
id: ID<B>,
depth: Depth & DepthsIn<B>
⚠️ undocumented
BinaryCoStream.subscribe<B, Depth>(id, as, depth, listener): () => void
B extends BinaryCoStream
this: CoValueClass<B>,
id: ID<B>,
as: Account,
depth: Depth & DepthsIn<B>,
listener: (value: DeeplyLoaded<B, Depth>) => void
⚠️ undocumented
BinaryCoStream.subscribeEf<B, Depth>(id, depth): Stream<DeeplyLoaded<B, Depth>, "unavailable", AccountCtx>
B extends BinaryCoStream
this: CoValueClass<B>,
id: ID<B>,
depth: Depth & DepthsIn<B>
⚠️ undocumented
.ensureLoaded<B, Depth>(depth): Promise<DeeplyLoaded<B, Depth> | undefined>
B extends BinaryCoStream
this: B,
depth: Depth & DepthsIn<B>
⚠️ undocumented
.subscribe<B, Depth>(depth, listener): () => void
B extends BinaryCoStream
this: B,
depth: Depth & DepthsIn<B>,
listener: (value: DeeplyLoaded<B, Depth>) => void
⚠️ undocumented
Collaboration
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): { id: ID<BinaryCoStream>, _type: "BinaryCoStream", mimeType: string, fileName: string, totalSizeBytes: number, chunks: Uint8Array[], finished: boolean }
⚠️ undocumented
.[inspect](): { id: ID<BinaryCoStream>, _type: "BinaryCoStream", mimeType: string, fileName: string, totalSizeBytes: number, chunks: Uint8Array[], finished: boolean }
⚠️ undocumented
Internals
BinaryCoStream.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawBinaryCoStream<{ type: "binary" }>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "BinaryCoStream"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
BinaryCoStream.create<S>(options): S
S extends BinaryCoStream
this: CoValueClass<S>,
options: { owner: Group | Account }
⚠️ undocumented
BinaryCoStream.loadAsBlob(id, as, options): Promise<Blob | undefined>
id: ID<BinaryCoStream>,
as: Account,
options: { allowUnfinished: boolean }
⚠️ undocumented
BinaryCoStream.createFromBlob(blob, options): Promise<BinaryCoStream>
blob: File | Blob,
options: { owner: Group | Account, onProgress: (progress: number) => void }
⚠️ undocumented
.constructor: NO TYPE
⚠️ undocumented
.getChunks(options): BinaryStreamInfo & { chunks: Uint8Array[], finished: boolean } | undefined
options: { allowUnfinished: boolean }
⚠️ undocumented
.start(options): void
options: BinaryStreamInfo
⚠️ undocumented
.push(data): void
data: Uint8Array
⚠️ undocumented
.end(): void
⚠️ undocumented
.toBlob(options): Blob | undefined
options: { allowUnfinished: boolean }
⚠️ undocumented
._loadedAs: Account
⚠️ undocumented

Identity & Permissions

class Group

Content
.id: ID<Group>
⚠️ undocumented
Subscription & Loading
Group.load<G, Depth>(id, as, depth): Promise<DeeplyLoaded<G, Depth> | undefined>
G extends Group
this: CoValueClass<G>,
id: ID<G>,
as: Account,
depth: Depth & DepthsIn<G>
⚠️ undocumented
Group.loadEf<G, Depth>(id, depth): Effect<DeeplyLoaded<G, Depth>, "unavailable", AccountCtx>
G extends Group
this: CoValueClass<G>,
id: ID<G>,
depth: Depth & DepthsIn<G>
⚠️ undocumented
Group.subscribe<G, Depth>(id, as, depth, listener): () => void
G extends Group
this: CoValueClass<G>,
id: ID<G>,
as: Account,
depth: Depth & DepthsIn<G>,
listener: (value: DeeplyLoaded<G, Depth>) => void
⚠️ undocumented
Group.subscribeEf<G, Depth>(id, depth): Stream<DeeplyLoaded<G, Depth>, "unavailable", AccountCtx>
G extends Group
this: CoValueClass<G>,
id: ID<G>,
depth: Depth & DepthsIn<G>
⚠️ undocumented
.ensureLoaded<G, Depth>(depth): Promise<DeeplyLoaded<G, Depth> | undefined>
G extends Group
this: G,
depth: Depth & DepthsIn<G>
⚠️ undocumented
.subscribe<G, Depth>(depth, listener): () => void
G extends Group
this: G,
depth: Depth & DepthsIn<G>,
listener: (value: DeeplyLoaded<G, Depth>) => void
⚠️ undocumented
Collaboration
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any[] | object
⚠️ undocumented
.[inspect](): any[] | object
⚠️ undocumented
Internals
Group.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawGroup<JsonObject | null>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "Group"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
Group._schema: any
⚠️ undocumented
Group.create<G>(options): G
G extends Group
this: CoValueClass<G>,
options: { owner: Account }
⚠️ undocumented
.constructor: NO TYPE
⚠️ undocumented
._schema: { profile: Schema, root: Schema, [MembersSym]: RefEncoded<Account> }
⚠️ undocumented
.profile: Profile | null
⚠️ undocumented
.root: CoMap | null
⚠️ undocumented
._refs: { profile: (TODO type conditional) | undefined, root: (TODO type conditional) | undefined }
⚠️ undocumented
.myRole(): Role | undefined
⚠️ undocumented
.addMember(member, role): Group
member: Account | "everyone",
role: Role
⚠️ undocumented
.members: { id: ID<this[MembersSym]> | "everyone", role: Role | undefined, ref: Ref<NonNullable<this[MembersSym]>> | undefined, account: NO TYPE }[]
⚠️ undocumented
[MembersSym]: Account | null
⚠️ undocumented
._loadedAs: Account
⚠️ undocumented

class Profile

Declaration
Profile.Record<Value>(value): RecordLikeCoMap
value: IfCo<Value, Value>
Declare a Record-like CoMap schema, by extending CoMap.Record(...) and passing the value schema using co. Keys are always string.
Example:
import { co, CoMap } from "jazz-tools";

class ColorToFruitMap extends CoMap.Record(
co.ref(Fruit)
) {}

// assume we have map: ColorToFruitMap
// and strawberry: Fruit
map["red"] = strawberry;
Content
.id: ID<Profile>
The ID of this CoMap
._refs: {[Key in string]: IfCo<this[Key], RefIfCoValue<this[Key]>>}
If property prop is a co.ref(...), you can use coMaps._refs.prop to access
the
Ref instead of the potentially loaded/null value.

This allows you to always get the ID or load the value manually.
Example:
person._refs.pet.id; // => ID<Animal>
person._refs.pet.value;
// => Animal | null
const pet = await person._refs.pet.load();
Creation
Profile.create<M>(init, options): M
M extends CoMap
this: CoValueClass<M>,
init: Simplify<CoMapInit<M>>,
options: { owner: Group | Account }
Create a new CoMap with the given initial values and owner.

The owner (a Group or Account) determines access rights to the CoMap.

The CoMap will immediately be persisted and synced to connected peers.
Example:
const person = Person.create({
name: "Alice",
age: 42,
pet: cat,
}, { owner: friendGroup });
Subscription & Loading
Profile.load<M, Depth>(id, as, depth): Promise<DeeplyLoaded<M, Depth> | undefined>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
as: Account,
depth: Depth & DepthsIn<M>
Load a CoMap with a given ID, as a given account.

depth specifies which (if any) fields that reference other CoValues to load as well before resolving.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or {} for shallowly loading only this CoMap, or { fieldA: depthA, fieldB: depthB } for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.
Example:
const person = await Person.load(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ pet: {} }
);
Profile.loadEf<M, Depth>(id, depth): Effect<DeeplyLoaded<M, Depth>, "unavailable", AccountCtx>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
depth: Depth & DepthsIn<M>
Effectful version of CoMap.load().

Needs to be run inside an
AccountCtx context.
Profile.subscribe<M, Depth>(id, as, depth, listener): () => void
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
as: Account,
depth: Depth & DepthsIn<M>,
listener: (value: DeeplyLoaded<M, Depth>) => void
Load and subscribe to a CoMap with a given ID, as a given account.

Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.

depth specifies which (if any) fields that reference other CoValues to load as well before calling listener for the first time.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or {} for shallowly loading only this CoMap, or { fieldA: depthA, fieldB: depthB } for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.

Returns an unsubscribe function that you should call when you no longer need updates.

Also see the
useCoState hook to reactively subscribe to a CoValue in a React component.
Example:
const unsub = Person.subscribe(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ pet: {} },
(person) => console.log(person)
);
Profile.subscribeEf<M, Depth>(id, depth): Stream<DeeplyLoaded<M, Depth>, "unavailable", AccountCtx>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
depth: Depth & DepthsIn<M>
Effectful version of CoMap.subscribe() that returns a stream of updates.

Needs to be run inside an
AccountCtx context.
.ensureLoaded<M, Depth>(depth): Promise<DeeplyLoaded<M, Depth> | undefined>
M extends CoMap
this: M,
depth: Depth & DepthsIn<M>
Given an already loaded CoMap, ensure that the specified fields are loaded to the specified depth.

Works like
CoMap.load(), but you don't need to pass the ID or the account to load as again.
.subscribe<M, Depth>(depth, listener): () => void
M extends CoMap
this: M,
depth: Depth & DepthsIn<M>,
listener: (value: DeeplyLoaded<M, Depth>) => void
Given an already loaded CoMap, subscribe to updates to the CoMap and ensure that the specified fields are loaded to the specified depth.

Works like
CoMap.subscribe(), but you don't need to pass the ID or the account to load as again.

Returns an unsubscribe function that you should call when you no longer need updates.
Collaboration
._edits: {[Key in string]: IfCo<this[Key], CoMapEdit<this[Key]>>}
⚠️ undocumented
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any
⚠️ undocumented
.[inspect](): any
⚠️ undocumented
Internals
Profile.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawCoMap<{ undefined }, JsonObject | null>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "CoMap"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
.name: co<string>
⚠️ undocumented

class Account

Content
.id: ID<Account>
⚠️ undocumented
Subscription & Loading
Account.load<A, Depth>(id, as, depth): Promise<DeeplyLoaded<A, Depth> | undefined>
A extends Account
this: CoValueClass<A>,
id: ID<A>,
as: Account,
depth: Depth & DepthsIn<A>
⚠️ undocumented
Account.loadEf<A, Depth>(id, depth): Effect<DeeplyLoaded<A, Depth>, "unavailable", AccountCtx>
A extends Account
this: CoValueClass<A>,
id: ID<A>,
depth: Depth & DepthsIn<A>
⚠️ undocumented
Account.subscribe<A, Depth>(id, as, depth, listener): () => void
A extends Account
this: CoValueClass<A>,
id: ID<A>,
as: Account,
depth: Depth & DepthsIn<A>,
listener: (value: DeeplyLoaded<A, Depth>) => void
⚠️ undocumented
Account.subscribeEf<A, Depth>(id, depth): Stream<DeeplyLoaded<A, Depth>, "unavailable", AccountCtx>
A extends Account
this: CoValueClass<A>,
id: ID<A>,
depth: Depth & DepthsIn<A>
⚠️ undocumented
.ensureLoaded<A, Depth>(depth): Promise<DeeplyLoaded<A, Depth> | undefined>
A extends Account
this: A,
depth: Depth & DepthsIn<A>
⚠️ undocumented
.subscribe<A, Depth>(depth, listener): () => void
A extends Account
this: A,
depth: Depth & DepthsIn<A>,
listener: (value: DeeplyLoaded<A, Depth>) => void
⚠️ undocumented
Collaboration
._owner: Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any[] | object
⚠️ undocumented
.[inspect](): any[] | object
⚠️ undocumented
Internals
Account.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawControlledAccount<AccountMeta> | RawAccount<AccountMeta>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "Account"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
Account._schema: any
⚠️ undocumented
Account.create<A>(options): Promise<A>
A extends Account
this: CoValueClass<A> & Account,
options: { creationProps: { name: string }, crypto: CryptoProvider<any>, initialAgentSecret: TODO type templateLiteral, peersToLoadFrom: Peer[] }
⚠️ undocumented
Account.become<A>(options): Promise<A>
A extends Account
this: CoValueClass<A> & Account,
options: { accountID: ID<Account>, accountSecret: TODO type templateLiteral, peersToLoadFrom: Peer[], crypto: CryptoProvider<any>, sessionID: TODO type templateLiteral | TODO type templateLiteral }
⚠️ undocumented
Account.createAs<A>(as, options): Promise<A>
A extends Account
this: CoValueClass<A> & Account,
as: Account,
options: { creationProps: { name: string } }
⚠️ undocumented
Account.fromNode<A>(node): A
A extends Account
this: CoValueClass<A>,
node: LocalNode
⚠️ undocumented
.constructor: NO TYPE
⚠️ undocumented
._schema: { profile: Schema, root: Schema }
⚠️ undocumented
._loadedAs: Account
⚠️ undocumented
.profile: Profile | null
⚠️ undocumented
.root: CoMap | null
⚠️ undocumented
._refs: { profile: RefIfCoValue<this["profile"]> | undefined, root: RefIfCoValue<this["root"]> | undefined }
⚠️ undocumented
.isMe: boolean
⚠️ undocumented
.sessionID: TODO type templateLiteral | TODO type templateLiteral | undefined
⚠️ undocumented
.myRole(): "admin" | undefined
⚠️ undocumented
.acceptInvite<V>(valueID, inviteSecret, coValueClass): Promise<(TODO type conditional) | undefined>
V extends CoValue
valueID: ID<V>,
inviteSecret: TODO type templateLiteral,
coValueClass: CoValueClass<V>
⚠️ undocumented
.migrate(creationProps): Promise<void> | void
this: Account,
creationProps: { name: string }
⚠️ undocumented
isControlledAccount(account): account is Account & { isMe: true, sessionID: TODO type templateLiteral | TODO type templateLiteral, _raw: RawControlledAccount<AccountMeta> }
account: Account

Schema definition

Encoders reflection

co reflection

type co

type co<T> = T & CoMarker | T

⚠️ undocumented

Abstract interfaces

interface CoValue

Content
.id: ID<CoValue>
⚠️ undocumented
Collaboration
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any[] | object
⚠️ undocumented
.[inspect](): any
⚠️ undocumented
Internals
._raw: RawCoValue
⚠️ undocumented
Type Helpers
._type: string
⚠️ undocumented

interface CoValueClass<Value>

Value extends CoValue

Media

class ImageDefinition

Declaration
ImageDefinition.Record<Value>(value): RecordLikeCoMap
value: IfCo<Value, Value>
Declare a Record-like CoMap schema, by extending CoMap.Record(...) and passing the value schema using co. Keys are always string.
Example:
import { co, CoMap } from "jazz-tools";

class ColorToFruitMap extends CoMap.Record(
co.ref(Fruit)
) {}

// assume we have map: ColorToFruitMap
// and strawberry: Fruit
map["red"] = strawberry;
Content
.id: ID<ImageDefinition>
The ID of this CoMap
._refs: {[Key in string]: IfCo<this[Key], RefIfCoValue<this[Key]>>}
If property prop is a co.ref(...), you can use coMaps._refs.prop to access
the
Ref instead of the potentially loaded/null value.

This allows you to always get the ID or load the value manually.
Example:
person._refs.pet.id; // => ID<Animal>
person._refs.pet.value;
// => Animal | null
const pet = await person._refs.pet.load();
Creation
ImageDefinition.create<M>(init, options): M
M extends CoMap
this: CoValueClass<M>,
init: Simplify<CoMapInit<M>>,
options: { owner: Group | Account }
Create a new CoMap with the given initial values and owner.

The owner (a Group or Account) determines access rights to the CoMap.

The CoMap will immediately be persisted and synced to connected peers.
Example:
const person = Person.create({
name: "Alice",
age: 42,
pet: cat,
}, { owner: friendGroup });
Subscription & Loading
ImageDefinition.load<M, Depth>(id, as, depth): Promise<DeeplyLoaded<M, Depth> | undefined>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
as: Account,
depth: Depth & DepthsIn<M>
Load a CoMap with a given ID, as a given account.

depth specifies which (if any) fields that reference other CoValues to load as well before resolving.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or {} for shallowly loading only this CoMap, or { fieldA: depthA, fieldB: depthB } for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.
Example:
const person = await Person.load(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ pet: {} }
);
ImageDefinition.loadEf<M, Depth>(id, depth): Effect<DeeplyLoaded<M, Depth>, "unavailable", AccountCtx>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
depth: Depth & DepthsIn<M>
Effectful version of CoMap.load().

Needs to be run inside an
AccountCtx context.
ImageDefinition.subscribe<M, Depth>(id, as, depth, listener): () => void
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
as: Account,
depth: Depth & DepthsIn<M>,
listener: (value: DeeplyLoaded<M, Depth>) => void
Load and subscribe to a CoMap with a given ID, as a given account.

Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.

depth specifies which (if any) fields that reference other CoValues to load as well before calling listener for the first time.
The
DeeplyLoaded return type guarantees that corresponding referenced CoValues are loaded to the specified depth.

You can pass
[] or {} for shallowly loading only this CoMap, or { fieldA: depthA, fieldB: depthB } for recursively loading referenced CoValues.

Check out the
load methods on CoMap/CoList/CoStream/Group/Account to see which depth structures are valid to nest.

Returns an unsubscribe function that you should call when you no longer need updates.

Also see the
useCoState hook to reactively subscribe to a CoValue in a React component.
Example:
const unsub = Person.subscribe(
"co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
me,
{ pet: {} },
(person) => console.log(person)
);
ImageDefinition.subscribeEf<M, Depth>(id, depth): Stream<DeeplyLoaded<M, Depth>, "unavailable", AccountCtx>
M extends CoMap
this: CoValueClass<M>,
id: ID<M>,
depth: Depth & DepthsIn<M>
Effectful version of CoMap.subscribe() that returns a stream of updates.

Needs to be run inside an
AccountCtx context.
.ensureLoaded<M, Depth>(depth): Promise<DeeplyLoaded<M, Depth> | undefined>
M extends CoMap
this: M,
depth: Depth & DepthsIn<M>
Given an already loaded CoMap, ensure that the specified fields are loaded to the specified depth.

Works like
CoMap.load(), but you don't need to pass the ID or the account to load as again.
.subscribe<M, Depth>(depth, listener): () => void
M extends CoMap
this: M,
depth: Depth & DepthsIn<M>,
listener: (value: DeeplyLoaded<M, Depth>) => void
Given an already loaded CoMap, subscribe to updates to the CoMap and ensure that the specified fields are loaded to the specified depth.

Works like
CoMap.subscribe(), but you don't need to pass the ID or the account to load as again.

Returns an unsubscribe function that you should call when you no longer need updates.
Collaboration
._edits: {[Key in string]: IfCo<this[Key], CoMapEdit<this[Key]>>}
⚠️ undocumented
._owner: Group | Account
⚠️ undocumented
Stringifying & Inspection
.toJSON(): any
⚠️ undocumented
.[inspect](): any
⚠️ undocumented
Internals
ImageDefinition.fromRaw<V>(raw): V
V extends CoValue
this: CoValueClass<V>,
raw: RawCoValue
⚠️ undocumented
._raw: RawCoMap<{ undefined }, JsonObject | null>
⚠️ undocumented
._instanceID: string
⚠️ undocumented
Type Helpers
._type: "CoMap"
⚠️ undocumented
.castAs<Cl>(cl): InstanceType<Cl>
Cl extends CoValueClass<CoValue> & CoValueFromRaw<CoValue>
cl: Cl
⚠️ undocumented
Other
.originalSize: co<[number, number]>
⚠️ undocumented
.highestResAvailable(options): { res: TODO type templateLiteral, stream: BinaryCoStream } | undefined
options: { maxWidth: number }
⚠️ undocumented
[ItemsSym]: co<BinaryCoStream | null>
⚠️ undocumented
.placeholderDataURL: co<string>
⚠️ undocumented

Other

type InviteSecret

type InviteSecretundefined = TODO type templateLiteral

⚠️ undocumented

type SessionID

type SessionIDundefined = TODO type templateLiteral

⚠️ undocumented

type AgentID

type AgentIDundefined = TODO type templateLiteral

⚠️ undocumented

type SyncMessage

type SyncMessageundefined = DoneMessage | NewContentMessage | KnownStateMessage | LoadMessage

⚠️ undocumented

type CoMapInit

type CoMapInit<Map extends object> = {[Key in CoKeys<Map>]: Map[Key]} & {[Key in CoKeys<Map>]: Map[Key]}

⚠️ undocumented

type DepthsIn

type DepthsIn<V, DepthLimit extends number, CurrentDepth extends number[]> = never[] | (TODO type conditional)

⚠️ undocumented

type DeeplyLoaded

type DeeplyLoaded<V, Depth, DepthLimit extends number, CurrentDepth extends number[]> = TODO type conditional

⚠️ undocumented
loadCoValue<V, Depth>(cls, id, as, depth): Promise<DeeplyLoaded<V, Depth> | undefined>
cls: CoValueClass<V>,
id: ID<V>,
as: Account,
depth: Depth & DepthsIn<V>
loadCoValueEf<V, Depth>(cls, id, depth): Effect.Effect<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx>
cls: CoValueClass<V>,
id: ID<V>,
depth: Depth & DepthsIn<V>
subscribeToCoValue<V, Depth>(cls, id, as, depth, listener): () => void
cls: CoValueClass<V>,
id: ID<V>,
as: Account,
depth: Depth & DepthsIn<V>,
listener: (value: DeeplyLoaded<V, Depth>) => void
subscribeToCoValueEf<V, Depth>(cls, id, depth): Stream.Stream<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx>
cls: CoValueClass<V>,
id: ID<V>,
depth: Depth & DepthsIn<V>

jazz-react

Context & Hooks

createJazzReactContext<Acc>({ auth, peer, storage? }): JazzReactContext<Acc>
{ auth: ReactAuthHook<Acc>, peer: TODO type templateLiteral | TODO type templateLiteral, storage: "experimentalOPFSdoNotUseOrYouWillBeFired" | "indexedDB" }

interface JazzReactContext<Acc>

Acc extends Account
Hooks
.useAccount(): { me: Acc, logOut: () => void }
⚠️ undocumented
.useAccount<D>(depth): { me: DeeplyLoaded<Acc, D> | undefined, logOut: () => void }
D extends { profile: DepthsIn<Profile, 5, [0]> | undefined, root: DepthsIn<CoMap, 5, [0]> | undefined } | never[]
depth: D
⚠️ undocumented
.useCoState<V, D>(Schema, id, depth): DeeplyLoaded<V, D> | undefined
V extends CoValue
Schema: TODO reflection type 512 & CoValueClass<CoValue>,
id: ID<V> | undefined,
depth: TODO type unknown
⚠️ undocumented
.useAcceptInvite<V>({ invitedObjectSchema, onAccept, forValueHint? }): void
V extends CoValue
{ invitedObjectSchema: CoValueClass<V>, onAccept: (projectID: ID<V>) => void, forValueHint: string }
⚠️ undocumented
Provider Component
.Provider: FC<{ children: ReactNode, loading: ReactNode }>
⚠️ undocumented

Media

useProgressiveImg({ image, maxWidth? }): { src: undefined | string, originalSize: co<[number, number]> | undefined }
{ image: ImageDefinition | null | undefined, maxWidth: number }
ProgressiveImg({ children, image, maxWidth? }): ReactNode
{ children: (result: { src: undefined | string, originalSize: TODO type typeOperator | undefined }) => ReactNode, image: ImageDefinition | null | undefined, maxWidth: number }

Auth Providers

type ReactAuthHook

type ReactAuthHook<Acc extends Account> = (setJazzAuthState: (state: AuthState) => void) => { auth: AuthProvider<Acc>, AuthUI: React.ReactNode, logOut: () => void }

⚠️ undocumented
DemoAuth<Acc>({ appName, accountSchema?, appHostname?, Component?, seedAccounts? }): ReactAuthHook<Acc>
{ appName: string, accountSchema: CoValueClass<Acc> & Account, appHostname: string, Component: Component, seedAccounts: { undefined } }

DemoAuth

PasskeyAuth<Acc>({ accountSchema, appName, appHostname?, Component? }): ReactAuthHook<Acc>
{ accountSchema: CoValueClass<Acc> & Account, appName: string, appHostname: string, Component: Component }

PasskeyAuth

PassphraseAuth<Acc>({ accountSchema, appName, wordlist, appHostname?, Component? }): ReactAuthHook<Acc>
{ accountSchema: CoValueClass<Acc> & Account, appName: string, wordlist: string[], appHostname: string, Component: Component }

PassphraseAuth

Invite Links

Other

type AuthState

type AuthStateundefined = "signedIn" | "ready" | "loading"

⚠️ undocumented

jazz-browser

Context Creation

createJazzBrowserContext<Acc>({ auth, peer, reconnectionTimeout?, storage?, crypto? }): Promise<BrowserContext<Acc>>
{ auth: AuthProvider<Acc>, peer: TODO type templateLiteral | TODO type templateLiteral, reconnectionTimeout: number, storage: "experimentalOPFSdoNotUseOrYouWillBeFired" | "indexedDB", crypto: CryptoProvider<any> }

type BrowserContext

type BrowserContext<Acc extends Account> = { me: Acc, done: () => void }

⚠️ undocumented

Auth Providers

type SessionProvider

type SessionProviderundefined = (accountID: AgentID | ID<Account>) => Promise<SessionID>

⚠️ undocumented

type SessionHandle

type SessionHandleundefined = { session: Promise<SessionID>, done: () => void }

⚠️ undocumented

interface AuthProvider<Acc>

Acc extends Account

class BrowserDemoAuth<Acc>

Acc extends Account

BrowserDemoAuth

BrowserPasskeyAuth

BrowserPassphraseAuth

Invite Links

consumeInviteLinkFromWindowLocation<V>({ as, invitedObjectSchema, forValueHint? }): Promise<{ valueID: ID<V>, inviteSecret: InviteSecret, valueHint: string } | undefined>
{ as: Account, invitedObjectSchema: CoValueClass<V>, forValueHint: string }

Other

getSessionHandleFor(accountID): SessionHandle
accountID: ID<Account> | TODO type templateLiteral
createWebSocketPeer(syncAddress): Peer
syncAddress: string

jazz-browser-media-images

Image creation

createImage(imageBlobOrFile, options): Promise<ImageDefinition>
imageBlobOrFile: File | Blob,
options: { owner: Group | Account, maxSize: 2048 | 256 | 1024 }

jazz-nodejs

Context Creation

startWorker<Acc>({ accountID?, accountSecret?, sessionID?, syncServer?, accountSchema? }): Promise<{ worker: Acc }>
{ accountID: string, accountSecret: string, sessionID: string, syncServer: string, accountSchema: CoValueClass<Acc> & Account }