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.

See the full example here.

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 Projects.

// schema.ts
export const 
const Project: CoMapSchema<{
    name: z.z.ZodString;
}>
Project
= import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): CoMapSchema<{
    name: z.z.ZodString;
}>
export map
map
({
name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
(),
}); export const
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>
Organization
= import coco.
map<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>(shape: {
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}): CoMapSchema<...>
export map
map
({
name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
(),
// shared data between users of each organization
projects: CoListSchema<CoMapSchema<{
    name: z.z.ZodString;
}>>
projects
: import coco.
list<CoMapSchema<{
    name: z.z.ZodString;
}>>(element: CoMapSchema<{
    name: z.z.ZodString;
}>): CoListSchema<CoMapSchema<{
    name: z.z.ZodString;
}>>
export list
list
(
const Project: CoMapSchema<{
    name: z.z.ZodString;
}>
Project
),
}); export const
const ListOfOrganizations: CoListSchema<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>>
ListOfOrganizations
= import coco.
list<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>>(element: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>): CoListSchema<...>
export list
list
(
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>
Organization
);

Learn more about defining schemas.

Adding a list of Organizations to the user's Account

Let's add the list of Organizations to the user's Account root so they can access them.

// schema.ts
export const 
const JazzAccountRoot: CoMapSchema<{
    organizations: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
        projects: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
        }>>;
    }>>;
}>
JazzAccountRoot
= import coco.
map<{
    organizations: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
        projects: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
        }>>;
    }>>;
}>(shape: {
    organizations: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
        projects: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
        }>>;
    }>>;
}): CoMapSchema<...>
export map
map
({
organizations: CoListSchema<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>>
organizations
: import coco.
list<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>>(element: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>): CoListSchema<...>
export list
list
(
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>
Organization
),
}); export const
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        organizations: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
            projects: CoListSchema<CoMapSchema<{
                name: z.z.ZodString;
            }>>;
        }>>;
    }>;
    profile: CoProfileSchema<...>;
}>
JazzAccount
= import coco
.
account<{
    root: CoMapSchema<{
        organizations: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
            projects: CoListSchema<CoMapSchema<{
                name: z.z.ZodString;
            }>>;
        }>>;
    }>;
    profile: CoProfileSchema<...>;
}>(shape?: {
    root: CoMapSchema<{
        organizations: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
            projects: CoListSchema<CoMapSchema<{
                name: z.z.ZodString;
            }>>;
        }>>;
    }>;
    profile: CoProfileSchema<...>;
} | undefined): AccountSchema<...>
export account
account
({
root: CoMapSchema<{
    organizations: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
        projects: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
        }>>;
    }>>;
}>
root
:
const JazzAccountRoot: CoMapSchema<{
    organizations: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
        projects: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
        }>>;
    }>>;
}>
JazzAccountRoot
,
profile: CoProfileSchema<{}>profile: import coco.
profile<{}>(shape?: {
    name?: z.z.core.$ZodString<string>;
    inbox?: z.z.core.$ZodOptional<z.z.core.$ZodString>;
    inboxInvite?: z.z.core.$ZodOptional<z.z.core.$ZodString>;
} | undefined): CoProfileSchema<...>
export profile
profile
({}),
}) .
function withMigration(migration: (account: {
    root: {
        organizations: CoList<{
            name: string;
            projects: CoList<{
                name: string;
            } & CoMap>;
        } & CoMap>;
    } & CoMap;
    profile: {
        name: string;
        inbox: string | undefined;
        inboxInvite: string | undefined;
    } & CoMap;
} & {
    ...;
} & Account, creationProps?: {
    name: string;
}) => void): AccountSchema<...>
withMigration
((
account: {
    root: {
        organizations: CoList<{
            name: string;
            projects: CoList<{
                name: string;
            } & CoMap>;
        } & CoMap>;
    } & CoMap;
    profile: {
        name: string;
        inbox: string | undefined;
        inboxInvite: string | undefined;
    } & CoMap;
} & {
    ...;
} & Account
account
) => {
if (
account: {
    root: {
        organizations: CoList<{
            name: string;
            projects: CoList<{
                name: string;
            } & CoMap>;
        } & CoMap>;
    } & CoMap;
    profile: {
        name: string;
        inbox: string | undefined;
        inboxInvite: string | undefined;
    } & CoMap;
} & {
    ...;
} & Account
account
.
Account.root: {
    organizations: CoList<{
        name: string;
        projects: CoList<{
            name: string;
        } & CoMap>;
    } & CoMap>;
} & CoMap
root
=== var undefinedundefined) {
// Using a Group as an owner allows you to give access to other users const const organizationGroup: GrouporganizationGroup = class Group
@categoryIdentity & Permissions
Group
.
Group.create<Group>(this: CoValueClass<Group>, options?: {
    owner: Account;
} | Account): Group
create
();
const
const organizations: CoList<{
    name: string;
    projects: CoList<{
        name: string;
    } & CoMap>;
} & CoMap>
organizations
= import coco.
list<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>>(element: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>): CoListSchema<...>
export list
list
(
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>
Organization
).
create: (items: CoListInit<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>>, options?: {
    owner: Account | Group;
} | Account | Group) => CoList<...>
create
([
// Create the first Organization so users can start right away
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
    }>>;
}>
Organization
.
create: (init: {
    name: string;
    projects: CoList<({
        name: string;
    } & CoMap) | null>;
}, options?: Account | Group | {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | undefined) => {
    ...;
} & CoMap
create
(
{ name: stringname: "My organization",
projects: CoList<({
    name: string;
} & CoMap) | null>
projects
: import coco.
list<CoMapSchema<{
    name: z.z.ZodString;
}>>(element: CoMapSchema<{
    name: z.z.ZodString;
}>): CoListSchema<CoMapSchema<{
    name: z.z.ZodString;
}>>
export list
list
(
const Project: CoMapSchema<{
    name: z.z.ZodString;
}>
Project
).
create: (items: CoListInit<CoMapSchema<{
    name: z.z.ZodString;
}>>, options?: {
    owner: Account | Group;
} | Account | Group) => CoList<...>
create
([], const organizationGroup: GrouporganizationGroup),
}, const organizationGroup: GrouporganizationGroup, ), ]);
account: {
    root: {
        organizations: CoList<{
            name: string;
            projects: CoList<{
                name: string;
            } & CoMap>;
        } & CoMap>;
    } & CoMap;
    profile: {
        name: string;
        inbox: string | undefined;
        inboxInvite: string | undefined;
    } & CoMap;
} & {
    ...;
} & Account
account
.
Account.root: {
    organizations: CoList<{
        name: string;
        projects: CoList<{
            name: string;
        } & CoMap>;
    } & CoMap>;
} & CoMap
root
=
const JazzAccountRoot: CoMapSchema<{
    organizations: CoListSchema<CoMapSchema<{
        name: z.z.ZodString;
        projects: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
        }>>;
    }>>;
}>
JazzAccountRoot
.
create: (init: {
    organizations: CoList<({
        name: string;
        projects: CoList<({
            name: string;
        } & CoMap) | null> | null;
    } & CoMap) | null>;
}, options?: Account | Group | {
    ...;
} | undefined) => {
    ...;
} & CoMap
create
({
organizations: CoList<({
    name: string;
    projects: CoList<({
        name: string;
    } & CoMap) | null> | null;
} & CoMap) | null>
organizations
});
} });

This schema now allows users to create Organizations and add Projects 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.

This guide and the example app show you the first method.

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.ElementAcceptInvitePage() {
  const { 
const me: ({
    root: {
        organizations: (({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap & {
            $onError: never;
        }) | null)[] & CoList<({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap) | null>;
    } & {
        ...;
    } & CoMap;
} & {
    ...;
} & Account) | null | undefined
me
} =
useAccount<AccountSchema<{
    root: CoMapSchema<{
        organizations: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
            projects: CoListSchema<z.z.ZodObject<{
                name: z.z.ZodString;
            }, z.z.core.$strip>>;
        }>>;
    }>;
    profile: CoProfileSchema<...>;
}>, {
    ...;
}>(AccountSchema: AccountSchema<...>, options?: {
    ...;
} | undefined): {
    ...;
} (+1 overload)
useAccount
(
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        organizations: CoListSchema<CoMapSchema<{
            name: z.z.ZodString;
            projects: CoListSchema<z.z.ZodObject<{
                name: z.z.ZodString;
            }, z.z.core.$strip>>;
        }>>;
    }>;
    profile: CoProfileSchema<...>;
}>
JazzAccount
, {
resolve?: RefsToResolve<{
    ...;
} & Account, 10, []> | undefined
resolve
: {
root?: RefsToResolve<{
    organizations: CoList<({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap) | null> | null;
} & CoMap, 10, [...]> | undefined
root
: {
organizations?: RefsToResolve<CoList<({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null>, 10, [0, 0]> | undefined
organizations
: {
$each: RefsToResolve<{
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap, 10, [0, 0, 0]>
$each
: { $onError?: null | undefined$onError: null } } } },
}); const const onAccept: (organizationId: string) => voidonAccept = (organizationId: stringorganizationId: string) => { if (
const me: ({
    root: {
        organizations: (({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap & {
            $onError: never;
        }) | null)[] & CoList<({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap) | null>;
    } & {
        ...;
    } & CoMap;
} & {
    ...;
} & Account) | null | undefined
me
) {
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<z.z.ZodObject<{
        name: z.z.ZodString;
    }, z.z.core.$strip>>;
}>
Organization
.
load<true>(id: string, options?: {
    resolve?: RefsToResolve<{
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap, 10, []> | undefined;
    loadAs?: Account | AnonymousJazzAgent;
} | undefined): Promise<...>
load
(organizationId: stringorganizationId).
Promise<({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null>.then<void, never>(onfulfilled?: ((value: ({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<...>) | ... 1 more ... | undefined): Promise<...>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
((
organization: ({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null
organization
) => {
if (
organization: ({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null
organization
) {
// avoid duplicates const const ids: (string | undefined)[]ids =
const me: {
    root: {
        organizations: (({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap & {
            $onError: never;
        }) | null)[] & CoList<({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap) | null>;
    } & {
        ...;
    } & CoMap;
} & {
    ...;
} & Account
me
.
Account.root: {
    organizations: (({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap & {
        $onError: never;
    }) | null)[] & CoList<({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap) | null>;
} & {
    organizations: CoList<({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap) | null> | null;
} & CoMap
root
.
organizations: (({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap & {
    $onError: never;
}) | null)[] & CoList<({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null>
organizations
.
Array<T>.map<string | undefined>(callbackfn: (value: ({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap & {
    $onError: never;
}) | null, index: number, array: (({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap & {
    $onError: never;
}) | 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.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(
(
organization: ({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap & {
    $onError: never;
}) | null
organization
) =>
organization: ({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap & {
    $onError: never;
}) | null
organization
?.CoMap.id: string | undefined
The ID of this `CoMap`
@categoryContent
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.
@paramsearchElement The element to search for.@paramfromIndex The position in this array at which to begin searching for searchElement.
includes
(organizationId: stringorganizationId)) return;
const me: {
    root: {
        organizations: (({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap & {
            $onError: never;
        }) | null)[] & CoList<({
            name: string;
            projects: CoList<{
                name: string;
            }> | null;
        } & CoMap) | null>;
    } & {
        ...;
    } & CoMap;
} & {
    ...;
} & Account
me
.
Account.root: {
    organizations: (({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap & {
        $onError: never;
    }) | null)[] & CoList<({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap) | null>;
} & {
    organizations: CoList<({
        name: string;
        projects: CoList<{
            name: string;
        }> | null;
    } & CoMap) | null> | null;
} & CoMap
root
.
organizations: (({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap & {
    $onError: never;
}) | null)[] & CoList<({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null>
organizations
.
function push(...items: (({
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap) | null)[]): number (+1 overload)
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(
organization: {
    name: string;
    projects: CoList<{
        name: string;
    }> | null;
} & CoMap
organization
);
} }); } };
useAcceptInvite<CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<z.z.ZodObject<{
        name: z.z.ZodString;
    }, z.z.core.$strip>>;
}>>({ invitedObjectSchema, onAccept, forValueHint, }: {
    invitedObjectSchema: CoMapSchema<...>;
    onAccept: (valueID: string) => void;
    forValueHint?: string;
}): void
useAcceptInvite
({
invitedObjectSchema: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<z.z.ZodObject<{
        name: z.z.ZodString;
    }, z.z.core.$strip>>;
}>
invitedObjectSchema
:
const Organization: CoMapSchema<{
    name: z.z.ZodString;
    projects: CoListSchema<z.z.ZodObject<{
        name: z.z.ZodString;
    }, z.z.core.$strip>>;
}>
Organization
,
onAccept: (valueID: string) => voidonAccept, }); return <JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Accepting invite...</JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>; }

Further reading