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: CoMapSchema<{ name: z.z.ZodString; }>
import co
co.map({
map<{ name: z.z.ZodString; }>(shape: { name: z.z.ZodString; }): CoMapSchema<{ name: z.z.ZodString; }> export map
name: z.z.ZodString
name:import z
z.string(), }); export const
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
Organization =
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>
import co
co.map({
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
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 export string
projects:
projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>
import co
co.list(
list<CoMapSchema<{ name: z.z.ZodString; }>>(element: CoMapSchema<{ name: z.z.ZodString; }>): CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>> export list
Project), }); export const
const Project: CoMapSchema<{ name: z.z.ZodString; }>
ListOfOrganizations =
const ListOfOrganizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>
import co
co.list(
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
Organization);
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>
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: CoMapSchema<{ organizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>; }>
import co
co.map({
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
organizations:
organizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>
import co
co.list(
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
Organization), }); export const
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>
JazzAccount =
const JazzAccount: AccountSchema<{ root: CoMapSchema<{ organizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>; }>; profile: CoProfileSchema<...>; }>
import co
co .account({
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
root:
root: CoMapSchema<{ organizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>; }>
JazzAccountRoot,
const JazzAccountRoot: CoMapSchema<{ organizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>; }>
profile: CoProfileSchema<{}>
profile:import co
co.profile({}), }) .
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
withMigration((
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<...>
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; profile: { name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
root ===
Account.root: { organizations: CoList<{ name: string; projects: CoList<{ name: string; } & CoMap>; } & CoMap>; } & CoMap
var undefined
undefined) { // 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: CoList<{ name: string; projects: CoList<{ name: string; } & CoMap>; } & CoMap>
import co
co.list(
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
Organization).
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>
create([ // Create the first Organization so users can start right away
create: (items: CoListInit<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>, options?: { owner: Account | Group; } | Account | Group) => CoList<...>
Organization.
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>
create( {
create: (init: { name: string; projects: CoList<({ name: string; } & CoMap) | null>; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
name: string
name: "My organization",projects:
projects: CoList<({ name: string; } & CoMap) | null>
import co
co.list(
list<CoMapSchema<{ name: z.z.ZodString; }>>(element: CoMapSchema<{ name: z.z.ZodString; }>): CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>> export list
Project).
const Project: CoMapSchema<{ name: z.z.ZodString; }>
create([],
create: (items: CoListInit<CoMapSchema<{ name: z.z.ZodString; }>>, options?: { owner: Account | Group; } | Account | Group) => CoList<...>
const organizationGroup: Group
organizationGroup), },const organizationGroup: Group
organizationGroup, ), ]);account.
account: { root: { organizations: CoList<{ name: string; projects: CoList<{ name: string; } & CoMap>; } & CoMap>; } & CoMap; profile: { name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
root =
Account.root: { organizations: CoList<{ name: string; projects: CoList<{ name: string; } & CoMap>; } & CoMap>; } & CoMap
JazzAccountRoot.
const JazzAccountRoot: CoMapSchema<{ organizations: CoListSchema<CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<CoMapSchema<{ name: z.z.ZodString; }>>; }>>; }>
create({
create: (init: { organizations: CoList<({ name: string; projects: CoList<({ name: string; } & CoMap) | null> | null; } & CoMap) | null>; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
organizations }); } });
organizations: CoList<({ name: string; projects: CoList<({ name: string; } & CoMap) | null> | null; } & CoMap) | null>
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: ({ 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
useAccount(
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)
JazzAccount, {
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<...>; }>
resolve: {
resolve?: RefsToResolve<{ ...; } & Account, 10, []> | undefined
root: {
root?: RefsToResolve<{ organizations: CoList<({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null> | null; } & CoMap, 10, [...]> | undefined
organizations: {
organizations?: RefsToResolve<CoList<({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null>, 10, [0, 0]> | undefined
$each: {
$each: RefsToResolve<{ name: string; projects: CoList<{ name: string; }> | null; } & CoMap, 10, [0, 0, 0]>
$onError?: null | undefined
$onError: null } } } }, }); constconst onAccept: (organizationId: string) => void
onAccept = (organizationId: string
organizationId: string) => { if (me) {
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
Organization.
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<z.z.ZodObject<{ name: z.z.ZodString; }, z.z.core.$strip>>; }>
load(
load<true>(id: string, options?: { resolve?: RefsToResolve<{ name: string; projects: CoList<{ name: string; }> | null; } & CoMap, 10, []> | undefined; loadAs?: Account | AnonymousJazzAgent; } | undefined): Promise<...>
organizationId: string
organizationId).
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.then((organization) => { if (
organization: ({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null
organization) { // avoid duplicates const
organization: ({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null
const ids: (string | undefined)[]
ids =me.
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
root.
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
organizations.
organizations: (({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap & { $onError: never; }) | null)[] & CoList<({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null>
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.map( (organization) =>
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
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: { 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
root.
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
organizations.
organizations: (({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap & { $onError: never; }) | null)[] & CoList<({ name: string; projects: CoList<{ name: string; }> | null; } & CoMap) | null>
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.push(organization); } }); } };
organization: { name: string; projects: CoList<{ name: string; }> | null; } & CoMap
useAcceptInvite({
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
invitedObjectSchema:
invitedObjectSchema: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<z.z.ZodObject<{ name: z.z.ZodString; }, z.z.core.$strip>>; }>
Organization,
const Organization: CoMapSchema<{ name: z.z.ZodString; projects: CoListSchema<z.z.ZodObject<{ name: z.z.ZodString; }, z.z.core.$strip>>; }>
onAccept: (valueID: string) => void
onAccept, }); return <JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>Accepting invite...</JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>; }