Accounts & Migrations
CoValues as a graph of data rooted in accounts
Compared to traditional relational databases with tables and foreign keys, Jazz is more like a graph database, or GraphQL APIs — where CoValues can arbitrarily refer to each other and you can resolve references without having to do a join. (See Subscribing & deep loading).
To find all data related to a user, the account acts as a root node from where you can resolve all the data they have access to. These root references are modeled explicitly in your schema, distinguishing between data that is typically public (like a user's profile) and data that is private (like their messages).
Account.root
- private data a user cares about
Every Jazz app that wants to refer to per-user data needs to define a custom root CoMap
schema and declare it in a custom Account
schema as the root
field:
import {
import co
co,import z
z } from "jazz-tools"; constMyAppRoot =
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
import co
co.map({
map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }>(shape: { myChats: co.List<co.Map<{}, unknown, Account | Group>>; }): co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group> export map
myChats: co.List<co.Map<{}, unknown, Account | Group>>
myChats:import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Chat: co.Map<{}, unknown, Account | Group>
Chat), }); export constMyAppAccount =
const MyAppAccount: co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<BaseProfileShape>; }>
import co
co.
account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<BaseProfileShape>; }>(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<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
MyAppRoot,
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, 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
Account.profile
- public data associated with a user
The built-in Account
schema class comes with a default profile
field, which is a CoMap (in a Group with "everyone": "reader"
- so publicly readable permissions)
that is set up for you based on the username the AuthMethod
provides on account creation.
Their pre-defined schemas roughly look like this:
// ...somewhere in jazz-tools itself... const
Account =
const Account: co.Account<{ root: co.Map<{}, unknown, Account | Group>; profile: co.Profile<BaseProfileShape>; }>
import co
co.
account<{ root: co.Map<{}, unknown, Account | Group>; profile: co.Profile<BaseProfileShape>; }>(shape?: { root: co.Map<{}, unknown, Account | Group>; profile: co.Profile<...>; } | 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: co.Map<{}, unknown, Account | Group>
root:import co
co.map({}),
map<{}>(shape: {}): co.Map<{}, unknown, Account | Group> export map
profile: co.Profile<BaseProfileShape>
profile:import co
co.profile(), });
profile<BaseProfileShape>(shape?: (BaseProfileShape & Partial<DefaultProfileShape>) | undefined): co.Profile<BaseProfileShape> export profile
If you want to keep the default co.profile()
schema, but customise your account's private root
, all you have to do is define a new root
field in your account schema and use co.profile()
without options:
const
MyAppRoot =
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
import co
co.map({
map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }>(shape: { myChats: co.List<co.Map<{}, unknown, Account | Group>>; }): co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group> export map
myChats: co.List<co.Map<{}, unknown, Account | Group>>
myChats:import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Chat: co.Map<{}, unknown, Account | Group>
Chat), }); export constMyAppAccount =
const MyAppAccount: co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<BaseProfileShape>; }>
import co
co.
account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<BaseProfileShape>; }>(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<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
MyAppRoot,
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, 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
If you want to extend the profile
to contain additional fields (such as an avatar co.image()
), you can declare your own profile schema class using co.profile({...})
:
export const
MyAppRoot =
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
import co
co.map({
map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }>(shape: { myChats: co.List<co.Map<{}, unknown, Account | Group>>; }): co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group> export map
myChats: co.List<co.Map<{}, unknown, Account | Group>>
myChats:import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Chat: co.Map<{}, unknown, Account | Group>
Chat), }); export constMyAppProfile =
const MyAppProfile: co.Profile<{ name: z.z.ZodString; avatar: co.Optional<co.Map<{ original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema; originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; progressive: z.z.ZodBoolean; }, co.FileStream, Account | Group>>; }>
import co
co.profile({
profile<{ name: z.z.ZodString; avatar: co.Optional<co.Map<{ original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema; originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; progressive: z.z.ZodBoolean; }, co.FileStream, Account | Group>>; }>(shape?: ({ ...; } & Partial<...>) | undefined): co.Profile<...> export profile
name: z.z.ZodString & z.z.core.$ZodString<string>
name:import z
z.string(), // compatible with default Profile schema
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export string
avatar:
avatar: co.Optional<co.Map<{ original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema; originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; progressive: z.z.ZodBoolean; }, co.FileStream, Account | Group>>
import co
co.optional(
optional<co.Map<{ original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema; originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; progressive: z.z.ZodBoolean; }, co.FileStream, Account | Group>>(schema: co.Map<...>): co.Optional<...> export optional
import co
co.image()), }); export const
function image(): co.Image export image
MyAppAccount =
const MyAppAccount: co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<{ name: z.z.ZodString; avatar: co.Optional<...>; }>; }>
import co
co.
account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<{ name: z.z.ZodString; avatar: co.Optional<...>; }>; }>(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<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
MyAppRoot,
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
profile:
profile: co.Profile<{ name: z.z.ZodString; avatar: co.Optional<co.Map<{ original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema; originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; progressive: z.z.ZodBoolean; }, co.FileStream, Account | Group>>; }>
MyAppProfile, });
const MyAppProfile: co.Profile<{ name: z.z.ZodString; avatar: co.Optional<co.Map<{ original: import("/vercel/path0/packages/jazz-tools/dist/tools/internal").FileStreamSchema; originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; progressive: z.z.ZodBoolean; }, co.FileStream, Account | Group>>; }>
When using custom profile schemas, you need to take care of initializing the profile
field in a migration,
and set up the correct permissions for it. See Adding/changing fields to root and profile.
Resolving CoValues starting at profile
or root
To use per-user data in your app, you typically use useAccount
somewhere in a high-level component, pass it your custom Account schema and specify which references to resolve using a resolve query (see Subscribing & deep loading).
import {
function useAccount<A extends AccountClass<Account> | CoreAccountSchema, R extends ResolveQuery<A> = true>(AccountSchema?: A, options?: { resolve?: ResolveQueryStrict<A, R>; }): { me: co.loaded<A, R> | undefined | null; agent: AnonymousJazzAgent | co.loaded<A, true>; logOut: () => void; }
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 } from "jazz-tools/react"; functionfunction DashboardPageComponent(): React.JSX.Element
DashboardPageComponent() { const {me } =
const me: CoMapLikeLoaded<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefined
useAccount<co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<{ name: z.z.core.$ZodString<string>; inbox: z.z.core.$ZodOptional<z.z.core.$ZodString>; inboxInvite: z.z.core.$ZodOptional<z.z.core.$ZodString>; }>; }>, { ...; }>(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(MyAppAccount, {
const MyAppAccount: co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<{ name: z.z.core.$ZodString<string>; inbox: z.z.core.$ZodOptional<z.z.core.$ZodString>; inboxInvite: z.z.core.$ZodOptional<z.z.core.$ZodString>; }>; }>
resolve?: RefsToResolve<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account, 10, []> | undefined
Resolve query to specify which nested CoValues to load from the accountresolve: {profile: true,
profile?: RefsToResolve<{ readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profile, 10, [0]> | undefined
root: {
root?: RefsToResolve<{ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap, 10, [0]> | undefined
myChats?: RefsToResolve<CoList<({} & CoMap) | null>, 10, [0, 0]> | undefined
myChats: {$each?: RefsToResolve<{} & CoMap, 10, [0, 0, 0]> | undefined
$each: true }, } }}); return ( <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h1>Dashboard</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h1> {me ? ( <
const me: CoMapLikeLoaded<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefined
React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>Logged in as {me.
const me: CoMapLikeLoaded<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account, { ...; }, 10, []>
profile.
Account.profile: { readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profile
Profile.name: string
name}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p> <React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h2>My chats</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h2> {me.
const me: CoMapLikeLoaded<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account, { ...; }, 10, []>
root.
Account.root: { readonly myChats: readonly ({} & CoMap)[] & CoList<({} & CoMap) | null>; } & { readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap
myChats: readonly ({} & CoMap)[] & CoList<({} & CoMap) | null>
myChats.map<React.JSX.Element>(callbackfn: (value: {} & CoMap, index: number, array: readonly ({} & CoMap)[]) => React.JSX.Element, thisArg?: any): React.JSX.Element[] (+1 overload)
Calls a defined callback function on each element of an array, and returns an array that contains the results.map((chat: {} & CoMap
chat) => ( <class ChatPreview
ChatPreviewReact.Attributes.key?: React.Key | null | undefined
key={chat: {} & CoMap
chat.CoMap.$jazz: CoMapJazzApi<{} & 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
The ID of this `CoMap`id}chat: {} & CoMap
chat={chat: {} & CoMap
chat} /> ))} </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> ) : ( "Loading..." )} </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> ); }
Populating and evolving root
and profile
schemas with migrations
As you develop your app, you'll likely want to
- initialise data in a user's
root
andprofile
- add more data to your
root
andprofile
schemas
You can achieve both by overriding the migrate()
method on your Account
schema class.
When migrations run
Migrations are run after account creation and every time a user logs in. Jazz waits for the migration to finish before passing the account to your app's context.
Initialising user data after account creation
export const
MyAppAccount =
const MyAppAccount: co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<...>; }>; }>
import co
co.
account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<...>; }>; }>(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<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
MyAppRoot,
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; }, unknown, Account | Group>
profile:
profile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<co.Map<{}, unknown, Account | Group>>; }>
MyAppProfile, }).
const MyAppProfile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<co.Map<{}, unknown, Account | Group>>; }>
withMigration((
AccountSchema<{ root: CoMapSchema<{ myChats: CoListSchema<CoMapSchema<{}, unknown, Account | Group>>; }, unknown, Account | Group>; profile: CoProfileSchema<...>; }>.withMigration(migration: (account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account, creationProps?: { name: string; }) => void): co.Account<...>
account,
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account
creationProps?: {
creationProps: { name: string; } | undefined
name: string
name: string }) => { // we use has to check if the root has ever been set if (!account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & 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 myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): boolean
has("root")) {account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & 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 myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.set<"root">(key: "root", value: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | CoMapInit<{ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap>): void
Set the value of a key in the account.set("root", {myChats: never[]
myChats: [], }); } if (!account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & 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 myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): boolean
has("profile")) { constconst profileGroup: Group
profileGroup =class Group
Group.create(); // Unlike the root, we want the profile to be publicly readable.
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const profileGroup: Group
profileGroup.Group.makePublic(role?: "reader" | "writer"): Group
Make the group public, so that everyone can read it. Alias for `addMember("everyone", role)`.makePublic();account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & 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 myChats: CoList<({} & CoMap) | null> | null; } & CoMap) | null; readonly profile: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; } & Account>.set<"profile">(key: "profile", value: ({ readonly name: string; readonly bookmarks: CoList<({} & CoMap) | null> | null; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profile) | CoMapInit<...>): void
Set the value of a key in the account.set( "profile",MyAppProfile.
const MyAppProfile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<co.Map<{}, unknown, Account | Group>>; }>
create({
CoMapSchema<{ name: ZodString; bookmarks: CoListSchema<CoMapSchema<{}, unknown, Account | Group>>; } & DefaultProfileShape, unknown, Group>.create(init: { name: string; bookmarks: CoList<({} & CoMap) | null> | readonly (({} & CoMap) | {})[]; inbox?: string | undefined; inboxInvite?: string | undefined; }, options?: { owner?: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): { ...; } & CoMap (+1 overload)
name: string
name:creationProps?.
creationProps: { name: string; } | undefined
name: string | undefined
name ?? "New user",bookmarks: CoList<({} & CoMap) | null> | readonly (({} & CoMap) | {})[]
bookmarks:import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Bookmark: co.Map<{}, unknown, Account | Group>
Bookmark).create([],
CoListSchema<CoMapSchema<{}, unknown, Account | Group>>.create(items: readonly (({} & CoMap) | {})[], options?: { owner: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): CoListInstance<...> (+1 overload)
const profileGroup: Group
profileGroup), },const profileGroup: Group
profileGroup), ); } });
Adding/changing fields to root
and profile
To add new fields to your root
or profile
schemas, amend their corresponding schema classes with new fields,
and then implement a migration that will populate the new fields for existing users (by using initial data, or by using existing data from old fields).
To do deeply nested migrations, you might need to use the asynchronous $jazz.ensureLoaded()
method before determining whether the field already exists, or is simply not loaded yet.
Now let's say we want to add a myBookmarks
field to the root
schema:
const
MyAppRoot =
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>; }, unknown, Account | Group>
import co
co.map({
map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>; }>(shape: { ...; }): co.Map<...> export map
myChats: co.List<co.Map<{}, unknown, Account | Group>>
myChats:import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Chat: co.Map<{}, unknown, Account | Group>
Chat),myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>
myBookmarks:import co
co.optional(
optional<co.List<co.Map<{}, unknown, Account | Group>>>(schema: co.List<co.Map<{}, unknown, Account | Group>>): co.Optional<co.List<co.Map<{}, unknown, Account | Group>>> export optional
import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Bookmark: co.Map<{}, unknown, Account | Group>
Bookmark)), }); export constMyAppAccount =
const MyAppAccount: co.Account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>; }, unknown, Account | Group>; profile: co.Profile<...>; }>
import co
co.
account<{ root: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; myBookmarks: co.Optional<co.List<co.Map<{}, 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<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>; }, unknown, Account | Group>
MyAppRoot,
const MyAppRoot: co.Map<{ myChats: co.List<co.Map<{}, unknown, Account | Group>>; myBookmarks: co.Optional<co.List<co.Map<{}, unknown, Account | Group>>>; }, unknown, Account | Group>
profile:
profile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<co.Map<{}, unknown, Account | Group>>; }>
MyAppProfile, }).
const MyAppProfile: co.Profile<{ name: z.z.ZodString; bookmarks: co.List<co.Map<{}, unknown, Account | Group>>; }>
withMigration(async (
AccountSchema<{ root: CoMapSchema<{ myChats: CoListSchema<CoMapSchema<{}, unknown, Account | Group>>; myBookmarks: CoOptionalSchema<CoListSchema<CoMapSchema<{}, unknown, Account | Group>>>; }, unknown, Account | Group>; profile: CoProfileSchema<...>; }>.withMigration(migration: (account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, creationProps?: { name: string; }) => void): co.Account<...>
account) => { if (!
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & 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 myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.has(key: "root" | "profile"): boolean
has("root")) {account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & 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 myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.set<"root">(key: "root", value: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | CoMapInit<...>): void
Set the value of a key in the account.set("root", {myChats: never[]
myChats: [], }); } // We need to load the root field to check for the myContacts field const {root } = await
const root: { readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap
account.
account: { readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account
Account.$jazz: AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & 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.ensureLoaded({
AccountJazzApi<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account>.ensureLoaded<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, { ...; }>(this: AccountJazzApi<...>, options: { ...; }): Promise<...>
resolve: {
resolve: RefsToResolve<{ readonly root: ({ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap) | null; readonly profile: ({ ...; } & CoMap) | null; } & Account, 10, []>
root: true } }); // we specifically need to check for undefined, // because myBookmarks might simply be not loaded (`null`) yet if (
root?: RefsToResolve<{ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap, 10, [...]> | undefined
root.
const root: { readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap
myBookmarks: CoList<({} & CoMap) | null> | null | undefined
myBookmarks ===var undefined
undefined) {root.
const root: { readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap
CoMap.$jazz: CoMapJazzApi<{ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & 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<{ readonly myChats: CoList<({} & CoMap) | null> | null; readonly myBookmarks: CoList<({} & CoMap) | null> | null | undefined; } & CoMap>.set<"myBookmarks">(key: "myBookmarks", value: CoList<({} & CoMap) | null> | readonly (({} & CoMap) | CoMapInit<{} & CoMap> | null)[] | null | undefined): void
Set a value on the CoMapset("myBookmarks",import co
co.list(
list<co.Map<{}, unknown, Account | Group>>(element: co.Map<{}, unknown, Account | Group>): co.List<co.Map<{}, unknown, Account | Group>> export list
const Bookmark: co.Map<{}, unknown, Account | Group>
Bookmark).create([],
CoListSchema<CoMapSchema<{}, unknown, Account | Group>>.create(items: readonly (({} & CoMap) | {})[], options?: { owner: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): CoListInstance<...> (+1 overload)
class Group
Group.create())); } });
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group