How to share data between users through Organizations
This guide shows you how to share a set of CoValues between users. Different apps have different names for this concept, such as "teams" or "workspaces".
We'll use the term Organization.
Defining the schema for an Organization
Create a CoMap shared by the users of the same organization to act as a root (or "main database") for the shared data within an organization.
For this example, users within an Organization
will be sharing Project
s.
// schema.ts export const
Project =
const Project: co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>
import co
co.map({
map<{ name: z.z.ZodString; }>(shape: { name: z.z.ZodString; }): co.Map<{ name: z.z.ZodString; }, unknown, Account | Group> export map
name: z.z.ZodString
name:import z
z.string(), }); export const
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export string
Organization =
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
import co
co.map({
map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }>(shape: { name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }): co.Map<...> export map
name: z.z.ZodString
name:import z
z.string(), // shared data between users of each organization
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export string
projects:
projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>
import co
co.list(
list<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>(element: co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>): co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>> export list
Project), }); export const
const Project: co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>
ListOfOrganizations =
const ListOfOrganizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>
import co
co.list(
list<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>(element: co.Map<...>): co.List<...> export list
Organization);
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
Learn more about defining schemas.
Adding a list of Organizations to the user's Account
Let's add the list of Organization
s to the user's Account root
so they can access them.
// schema.ts export const
JazzAccountRoot =
const JazzAccountRoot: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>
import co
co.map({
map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }>(shape: { ...; }): co.Map<...> export map
organizations:
organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>
import co
co.list(
list<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>(element: co.Map<...>): co.List<...> export list
Organization), }); export const
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
JazzAccount =
const JazzAccount: co.Account<{ root: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<...>; }>
import co
co .
account<{ root: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<...>; }>(shape?: { ...; } | undefined): co.Account<...> export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.account({root:
root: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>
JazzAccountRoot,
const JazzAccountRoot: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>
profile: co.Profile<BaseProfileShape>
profile:import co
co.profile(), }) .
profile<BaseProfileShape>(shape?: (BaseProfileShape & Partial<DefaultProfileShape>) | undefined): co.Profile<BaseProfileShape> export profile
withMigration((
AccountSchema<{ root: CoMapSchema<{ organizations: CoListSchema<CoMapSchema<{ name: ZodString; projects: CoListSchema<CoMapSchema<{ name: ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>; profile: CoProfileSchema<...>; }>.withMigration(migration: (account: { readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, creationProps?: { name: string; }) => void): co.Account<...>
account) => { if (!
account: { readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
account.
account: { readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.$jazz.AccountJazzApi<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): boolean
has("root")) { // Using a Group as an owner allows you to give access to other users constconst organizationGroup: Group
organizationGroup =class Group
Group.create(); const
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
organizations =
const organizations: CoListInstance<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>
import co
co.list(
list<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>(element: co.Map<...>): co.List<...> export list
Organization).
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
create([ // Create the first Organization so users can start right away
CoListSchema<CoMapSchema<{ name: ZodString; projects: CoListSchema<CoMapSchema<{ name: ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>.create(items: readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | { name: string; projects: CoList<({ readonly name: string; } & CoMap) | null> | readonly (({ readonly name: string; } & CoMap) | { ...; })[]; })[], options?: { owner: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): CoListInstance<...> (+1 overload)
Organization.
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
create( {
CoMapSchema<{ name: ZodString; projects: CoListSchema<CoMapSchema<{ name: ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>.create(init: { name: string; projects: CoList<({ readonly name: string; } & CoMap) | null> | readonly (({ readonly name: string; } & CoMap) | { name: string; })[]; }, options?: { owner?: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): { ...; } & CoMap (+1 overload)
name: string
name: "My organization",projects:
projects: CoList<({ readonly name: string; } & CoMap) | null> | readonly (({ readonly name: string; } & CoMap) | { name: string; })[]
import co
co.list(
list<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>(element: co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>): co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>> export list
Project).
const Project: co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>
create([],
CoListSchema<CoMapSchema<{ name: ZodString; }, unknown, Account | Group>>.create(items: readonly (({ readonly name: string; } & CoMap) | { name: string; })[], options?: { owner: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): CoListInstance<...> (+1 overload)
const organizationGroup: Group
organizationGroup), },const organizationGroup: Group
organizationGroup, ), ]);account.
account: { readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.$jazz.
AccountJazzApi<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.set<"root">(key: "root", value: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | CoMapInit<...>): void
Set the value of a key in the account.set("root", {organizations }); } });
organizations: CoListInstance<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>
This schema now allows users to create Organization
s and add Project
s to them.
See the schema for the example app here.
Adding members to an Organization
Here are different ways to add members to an Organization
.
- Send users an invite link.
- The user requests to join.
This guide and the example app show you the first method.
Adding members through invite links
Here's how you can generate an invite link.
When the user accepts the invite, add the Organization
to the user's organizations
list.
export function
function AcceptInvitePage(): React.JSX.Element
AcceptInvitePage() { const {me } =
const me: CoMapLikeLoaded<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefined
useAccount<co.Account<{ root: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<...>; }>, { ...; }>(AccountSchema?: co.Account<...> | undefined, options?: { ...; } | undefined): { ...; }
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.useAccount(JazzAccount, {
const JazzAccount: co.Account<{ root: co.Map<{ organizations: co.List<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<...>; }>
resolve?: RefsToResolve<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, 10, []> | undefined
Resolve query to specify which nested CoValues to load from the accountresolve: {root: {
root?: RefsToResolve<{ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap, 10, [...]> | undefined
organizations: {
organizations?: RefsToResolve<CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>, 10, [0, 0]> | undefined
$each: {
$each?: RefsToResolve<{ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap, 10, [0, 0, 0]> | undefined
$onError?: null | undefined
$onError: null } } } }, }); constconst onAccept: (organizationId: string) => void
onAccept = (organizationId: string
organizationId: string) => { if (me) {
const me: CoMapLikeLoaded<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefined
Organization.
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
load(
CoMapSchema<{ name: ZodString; projects: CoListSchema<CoMapSchema<{ name: ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>.load<true>(id: string, options?: { resolve?: RefsToResolve<{ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap, 10, []> | undefined; loadAs?: Account | AnonymousJazzAgent; skipRetry?: boolean; } | undefined): Promise<...>
organizationId: string
organizationId).
Promise<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>.then<void, never>(onfulfilled?: ((value: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null) => void | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<...>) | ... 1 more ... | undefined): Promise<...>
Attaches callbacks for the resolution and/or rejection of the Promise.then((organization) => { if (
organization: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null
organization) { // avoid duplicates const
organization: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null
const ids: (string | undefined)[]
ids =me.
const me: CoMapLikeLoaded<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, { ...; }, 10, []>
root.
Account.root: { readonly organizations: readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null)[] & CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>; } & { readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap
organizations.
organizations: readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null)[] & CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>
map<string | undefined>(callbackfn: (value: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null, index: number, array: readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & ... 1 more ... & {}) | null)[]) => string | undefined, thisArg?: any): (string | undefined)[] (+1 overload)
Calls a defined callback function on each element of an array, and returns an array that contains the results.map( (organization) =>
organization: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null
organization?.
organization: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null
CoMap.$jazz: CoMapJazzApi<{ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}>
Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<M extends CoMap>.id: string | undefined
The ID of this `CoMap`id, ); if (const ids: (string | undefined)[]
ids.Array<string | undefined>.includes(searchElement: string | undefined, fromIndex?: number): boolean
Determines whether an array includes a certain element, returning true or false as appropriate.includes(organizationId: string
organizationId)) return;me.
const me: CoMapLikeLoaded<{ readonly root: ({ readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, { ...; }, 10, []>
root.
Account.root: { readonly organizations: readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null)[] & CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>; } & { readonly organizations: CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null> | null; } & CoMap
organizations.
organizations: readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null)[] & CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>
$jazz.
CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>.$jazz: CoListJazzApi<readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null)[] & CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>>
CoListJazzApi<readonly (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | null)[] & CoList<({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | null>>.push(...items: (({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | CoMapInit<{ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}> | null)[]): number
Appends new elements to the end of an array, and returns the new length of the array.push(organization); } }); } };
organization: { readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap
useAcceptInvite({
useAcceptInvite<co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>>({ invitedObjectSchema, onAccept, forValueHint, }: { ...; }): void
invitedObjectSchema:
invitedObjectSchema: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
Organization,
const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>
onAccept: (valueID: string) => void
onAccept, }); return <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>Accepting invite...</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>; }