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 {
class Account
Account,class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap } from "jazz-tools"; export classclass MyAppAccount
MyAppAccount extendsclass Account
Account {MyAppAccount.root: co<MyAppRoot | null>
root =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppRoot>(arg: typeof MyAppRoot | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppRoot), options?: never) => co<...> (+1 overload)
class MyAppRoot
MyAppRoot); } export classclass MyAppRoot
MyAppRoot extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {MyAppRoot.myChats: co<ListOfChats | null>
myChats =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref: <typeof ListOfChats>(arg: typeof ListOfChats | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfChats), options?: never) => co<ListOfChats | null> (+1 overload)
ref(class ListOfChats
ListOfChats);MyAppRoot.myContacts: co<ListOfAccounts | null>
myContacts =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref: <typeof ListOfAccounts>(arg: typeof ListOfAccounts | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfAccounts), options?: never) => co<...> (+1 overload)
ref(class ListOfAccounts
ListOfAccounts); } // Register the Account schema so `useAccount` returns our custom `MyAppAccount` declare module "jazz-react" { interface Register {Register.Account: MyAppAccount
Account:class MyAppAccount
MyAppAccount; } }
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... export class
class Account
Account extendsclass Group
Group {Account.profile: co<Profile | null>
profile =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof Profile>(arg: typeof Profile | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Profile), options?: never) => co<...> (+1 overload)
class Profile
Profile); } export classclass Profile
Profile extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {Profile.name: co<string>
name =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
string: co<string>
string; }
If you want to keep the default Profile
schema, but customise your account's private root
, all you have to do is define a new root
field in your account schema:
(You don't have to explicitly re-define the profile
field, but it makes it more readable that the Account contains both profile
and root
)
import {
class Account
Account,class Profile
Profile } from "jazz-tools"; export classclass MyAppAccount
MyAppAccount extendsclass Account
Account {MyAppAccount.profile: co<Profile | null>
profile =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof Profile>(arg: typeof Profile | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Profile), options?: never) => co<...> (+1 overload)
class Profile
Profile);MyAppAccount.root: co<MyAppRoot | null>
root =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppRoot>(arg: typeof MyAppRoot | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppRoot), options?: never) => co<...> (+1 overload)
class MyAppRoot
MyAppRoot); }
If you want to extend the profile
to contain additional fields (such as an avatar ImageDefinition
), you can declare your own profile schema class that extends Profile
:
import {
class Account
Account,class Profile
Profile,class ImageDefinition
ImageDefinition } from "jazz-tools"; export classclass MyAppAccount
MyAppAccount extendsclass Account
Account {MyAppAccount.profile: co<MyAppProfile | null>
profile =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppProfile>(arg: typeof MyAppProfile | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppProfile), options?: never) => co<...> (+1 overload)
class MyAppProfile
MyAppProfile);MyAppAccount.root: co<MyAppRoot | null>
root =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppRoot>(arg: typeof MyAppRoot | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppRoot), options?: never) => co<...> (+1 overload)
class MyAppRoot
MyAppRoot); } export classclass MyAppRoot
MyAppRoot extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {MyAppRoot.myChats: co<ListOfChats | null>
myChats =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref: <typeof ListOfChats>(arg: typeof ListOfChats | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfChats), options?: never) => co<ListOfChats | null> (+1 overload)
ref(class ListOfChats
ListOfChats);MyAppRoot.myContacts: co<ListOfAccounts | null>
myContacts =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref: <typeof ListOfAccounts>(arg: typeof ListOfAccounts | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfAccounts), options?: never) => co<...> (+1 overload)
ref(class ListOfAccounts
ListOfAccounts); } export classclass MyAppProfile
MyAppProfile extendsclass Profile
Profile {MyAppProfile.name: co<string>
name =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
string: co<string>
string; // compatible with default Profile schemaMyAppProfile.avatar: co<ImageDefinition | null | undefined>
avatar =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
optional.
optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; ... 7 more ...; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }
ref(
ref: <typeof ImageDefinition>(arg: typeof ImageDefinition | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof ImageDefinition)) => co<...>
class ImageDefinition
ImageDefinition); } // Register the Account schema so `useAccount` returns our custom `MyAppAccount` declare module "jazz-react" { interface Register {Register.Account: MyAppAccount
Account:class MyAppAccount
MyAppAccount; } }
Resolving CoValues starting at profile
or root
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 class
class MyAppAccount
MyAppAccount extendsclass Account
Account {MyAppAccount.root: co<MyAppRoot | null>
root =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppRoot>(arg: typeof MyAppRoot | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppRoot), options?: never) => co<...> (+1 overload)
class MyAppRoot
MyAppRoot);MyAppAccount.profile: co<MyAppProfile | null>
profile =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppProfile>(arg: typeof MyAppProfile | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppProfile), options?: never) => co<...> (+1 overload)
class MyAppProfile
MyAppProfile); asyncmigrate(
MyAppAccount.migrate(this: MyAppAccount, creationProps?: { name: string; }): Promise<void>
this: MyAppAccount
this:class MyAppAccount
MyAppAccount,creationProps?: {
creationProps: { name: string; } | undefined
name: string
name: string }) { // we specifically need to check for undefined, // because the root might simply be not loaded (`null`) yet if (this.MyAppAccount.root: co<MyAppRoot | null>
root ===var undefined
undefined) { this.MyAppAccount.root: co<MyAppRoot | null>
root =class MyAppRoot
MyAppRoot.
CoMap.create<MyAppRoot>(this: CoValueClass<...>, init: {}, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): MyAppRoot
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.create({ // Using a group to set the owner is always a good idea. // This way if in the future we want to share // this coValue we can do so easily.myChats: ListOfChats
myChats:class ListOfChats
ListOfChats.
CoList<Item>.create<ListOfChats>(this: CoValueClass<ListOfChats>, items: (Chat | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfChats
Create a new CoList with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoList will immediately be persisted and synced to connected peers.create([],class Group
Group.create()),
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
myContacts: ListOfAccounts
myContacts:class ListOfAccounts
ListOfAccounts.
CoList<Item>.create<ListOfAccounts>(this: CoValueClass<ListOfAccounts>, items: (Account | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfAccounts
Create a new CoList with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoList will immediately be persisted and synced to connected peers.create([],class Group
Group.create()) }); } if (this.
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
MyAppAccount.profile: co<MyAppProfile | null>
profile ===var undefined
undefined) { 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.addMember(member: Everyone, role: "writer" | "reader" | "writeOnly"): void (+1 overload)
addMember("everyone", "reader"); this.MyAppAccount.profile: co<MyAppProfile | null>
profile =class MyAppProfile
MyAppProfile.
Profile.create<MyAppProfile>(this: CoValueClass<...>, init: { name: co<string> & (co<string> | undefined); bookmarks: (ListOfBookmarks | NonNullable<ListOfBookmarks & CoMarker>) & (ListOfBookmarks | ... 1 more ... | undefined); inbox?: CoID<...> | ... 2 more ... | undefined; inboxInvite?: `${CoID<...>}/inviteSecret_z${string}` | ... 2 more ... | undefined; }, options?: { owner: Group; } | Group): MyAppProfile
Creates a new profile with the given initial values and owner. The owner (a Group) determines access rights to the Profile.create({name: co<string> & (co<string> | undefined)
name:creationProps?.
creationProps: { name: string; } | undefined
name: string | undefined
name ?? "New user",bookmarks: (ListOfBookmarks | NonNullable<ListOfBookmarks & CoMarker>) & (ListOfBookmarks | NonNullable<ListOfBookmarks & CoMarker> | undefined)
bookmarks:class ListOfBookmarks
ListOfBookmarks.
CoList<Item>.create<ListOfBookmarks>(this: CoValueClass<ListOfBookmarks>, items: (Bookmark | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfBookmarks
Create a new CoList with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoList will immediately be persisted and synced to connected peers.create([],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 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:
export class
class MyAppAccount
MyAppAccount extendsclass Account
Account {MyAppAccount.root: co<MyAppRoot | null>
root =co.
const co: { string: co<string>; number: co<number>; boolean: co<boolean>; null: co<null>; Date: co<Date>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>; encoded<T>(arg: Encoder<T>): co<T>; ref: { ...; }; items: ItemsSym; optional: { ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>; json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>; encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>; string: co<string | undefined>; number: co<number | undefined>; boolean: co<boolean | undefined>; null: co<null | undefined>; Date: co<Date | undefined>; literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>; }; }
ref(
ref: <typeof MyAppRoot>(arg: typeof MyAppRoot | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof MyAppRoot), options?: never) => co<...> (+1 overload)
class MyAppRoot
MyAppRoot); asyncMyAppAccount.migrate(this: MyAppAccount): Promise<void>
migrate(this: MyAppAccount
this:class MyAppAccount
MyAppAccount) { if (this.MyAppAccount.root: co<MyAppRoot | null>
root ===var undefined
undefined) { this.MyAppAccount.root: co<MyAppRoot | null>
root =class MyAppRoot
MyAppRoot.
CoMap.create<MyAppRoot>(this: CoValueClass<...>, init: { myChats: (ListOfChats | NonNullable<ListOfChats & CoMarker>) & (ListOfChats | NonNullable<...> | undefined); myContacts: (ListOfAccounts | NonNullable<...>) & (ListOfAccounts | ... 1 more ... | undefined); myBookmarks?: ListOfBookmarks | ... 2 more ... | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): MyAppRoot
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.create({myChats: (ListOfChats | NonNullable<ListOfChats & CoMarker>) & (ListOfChats | NonNullable<ListOfChats & CoMarker> | undefined)
myChats:class ListOfChats
ListOfChats.
CoList<Item>.create<ListOfChats>(this: CoValueClass<ListOfChats>, items: (Chat | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfChats
Create a new CoList with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoList will immediately be persisted and synced to connected peers.create([],class Group
Group.create()),
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
myContacts: (ListOfAccounts | NonNullable<ListOfAccounts & CoMarker>) & (ListOfAccounts | NonNullable<ListOfAccounts & CoMarker> | undefined)
myContacts:class ListOfAccounts
ListOfAccounts.
CoList<Item>.create<ListOfAccounts>(this: CoValueClass<ListOfAccounts>, items: (Account | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfAccounts
Create a new CoList with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoList will immediately be persisted and synced to connected peers.create([],class Group
Group.create()) }); } // We need to load the root field to check for the myContacts field const {
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const root: MyAppRoot & co<MyAppRoot | null>
root } = await this.ensureLoaded({
Account.ensureLoaded<MyAppAccount, { root: true; }>(this: MyAppAccount, options: { resolve: RefsToResolve<MyAppAccount, 10, []>; }): Promise<{ root: MyAppRoot; } & MyAppAccount>
resolve: RefsToResolve<MyAppAccount, 10, []>
resolve: {root?: RefsToResolve<MyAppRoot, 10, [0]> | undefined
root: true } }); // we specifically need to check for undefined, // because myBookmarks might simply be not loaded (`null`) yet if (const root: MyAppRoot & co<MyAppRoot | null>
root.MyAppRoot.myBookmarks: co<ListOfBookmarks | null | undefined>
myBookmarks ===var undefined
undefined) {const root: MyAppRoot & co<MyAppRoot | null>
root.MyAppRoot.myBookmarks: co<ListOfBookmarks | null | undefined>
myBookmarks =class ListOfBookmarks
ListOfBookmarks.
CoList<Item>.create<ListOfBookmarks>(this: CoValueClass<ListOfBookmarks>, items: (Bookmark | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfBookmarks
Create a new CoList with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoList will immediately be persisted and synced to connected peers.create([],class Group
Group.create()); } } }
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group