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 Projects.
// schema.ts export constProject =const Project: co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>import coco.map({map<{ name: z.z.ZodString; }>(shape: { name: z.z.ZodString; }): co.Map<{ name: z.z.ZodString; }, unknown, Account | Group> export mapname: z.z.ZodStringname:import zz.string(), }); export constfunction string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export stringOrganization =const Organization: co.Map<{ name: z.z.ZodString; projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>; }, unknown, Account | Group>import coco.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 mapname: z.z.ZodStringname:import zz.string(), // shared data between users of each organizationfunction string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export stringprojects:projects: co.List<co.Map<{ name: z.z.ZodString; }, unknown, Account | Group>>import coco.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 listProject), }); export constconst 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 coco.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 listOrganization);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 Organizations to the user's Account root so they can access them.
// schema.ts export constJazzAccountRoot =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 coco.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 maporganizations: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 coco.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 listOrganization), }); export constconst 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 coco .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 accountDefines 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 coco.profile(), }) .profile<BaseProfileShape>(shape?: (BaseProfileShape & Partial<DefaultProfileShape>) | undefined): co.Profile<BaseProfileShape> export profilewithMigration((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; } & Accountaccount.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; } & AccountAccount.$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"): booleanhas("root")) { // Using a Group as an owner allows you to give access to other users constconst organizationGroup: GrouporganizationGroup =class GroupGroup.create(); constGroup.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Grouporganizations =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 coco.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 listOrganization).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 awayCoListSchema<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: stringname: "My organization",projects:projects: CoList<({ readonly name: string; } & CoMap) | null> | readonly (({ readonly name: string; } & CoMap) | { name: string; })[]import coco.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 listProject).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: GrouporganizationGroup), },const organizationGroup: GrouporganizationGroup, ), ]);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; } & AccountAccount.$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<...>): voidSet 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 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.
- 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 functionfunction AcceptInvitePage(): React.JSX.ElementAcceptInvitePage() { 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 | undefineduseAccount<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, []> | undefinedResolve 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, [...]> | undefinedorganizations: {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) => voidonAccept = (organizationId: stringorganizationId: 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 | undefinedOrganization.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; unstable_branch?: BranchDefinition; } | undefined): Promise<...>organizationId: stringorganizationId).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) | nullorganization) { // avoid duplicates constorganization: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap) | nullconst 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; } & CoMaporganizations.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 & {}) | nullorganization?.organization: ({ readonly name: string; readonly projects: CoList<({ readonly name: string; } & CoMap) | null> | null; } & CoMap & {}) | nullCoMap.$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.CoValueJazzApi<V extends CoValue>.id: string | undefinedid, ); if (const ids: (string | undefined)[]ids.Array<string | undefined>.includes(searchElement: string | undefined, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.includes(organizationId: stringorganizationId)) 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; } & CoMaporganizations.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)[]): numberAppends 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; } & CoMapuseAcceptInvite({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, }: { ...; }): voidinvitedObjectSchema: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) => voidonAccept, }); 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>; }