Sharing data through Organizations
Organizations are a way to share a set of data 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 class
class Project
Project extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {Project.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; } export classclass ListOfProjects
ListOfProjects extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.CoList.
CoList<Item = any>.Of<co<Project | null>>(item: co<Project | null>): { new (options: { fromRaw: RawCoList; } | undefined): CoList<co<Project | null>>; ... 12 more ...; fromAsync<T>(iterableOrArrayLike: AsyncIterable<T> | Iterable<T | PromiseLike<T>> | ArrayLike<T | PromiseLike<T>>): Promise<T[]>; fromAsync<T, U>(iterableOrArrayLike: AsyncIterable<T> | Iterable<T> | ArrayLike<T>, mapFn: (value: Awaited<T>, index: number) => U, thisArg?: any): Promise<Awaited<U>[]>; }
Declare a `CoList` by subclassing `CoList.Of(...)` and passing the item schema using `co`.Of(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 Project>(arg: typeof Project | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Project), options?: never) => co<...> (+1 overload)
class Project
Project)) {} export classclass Organization
Organization extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {Organization.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; // shared data between users of each organizationOrganization.projects: co<ListOfProjects | null>
projects =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 ListOfProjects>(arg: typeof ListOfProjects | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfProjects), options?: never) => co<...> (+1 overload)
ref(class ListOfProjects
ListOfProjects); } export classclass ListOfOrganizations
ListOfOrganizations extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.CoList.
CoList<Item = any>.Of<co<Organization | null>>(item: co<Organization | null>): { new (options: { fromRaw: RawCoList; } | undefined): CoList<...>; ... 12 more ...; fromAsync<T>(iterableOrArrayLike: AsyncIterable<T> | Iterable<T | PromiseLike<T>> | ArrayLike<T | PromiseLike<T>>): Promise<T[]>; fromAsync<T, U>(iterableOrArrayLike: AsyncIterable<T> | Iterable<T> | ArrayLike<T>, mapFn: (value: Awaited<T>, index: number) => U, thisArg?: any): Promise<Awaited<U>[]>; }
Declare a `CoList` by subclassing `CoList.Of(...)` and passing the item schema using `co`.Of(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 Organization>(arg: typeof Organization | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Organization), options?: never) => co<...> (+1 overload)
class Organization
Organization)) {}
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 class
class JazzAccountRoot
JazzAccountRoot extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {JazzAccountRoot.organizations: co<ListOfOrganizations | null>
organizations =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 ListOfOrganizations>(arg: typeof ListOfOrganizations | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfOrganizations), options?: never) => co<...> (+1 overload)
ref(class ListOfOrganizations
ListOfOrganizations); } export classclass JazzAccount
JazzAccount extendsclass Account
Account {JazzAccount.root: co<JazzAccountRoot | 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 JazzAccountRoot>(arg: typeof JazzAccountRoot | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof JazzAccountRoot), options?: never) => co<...> (+1 overload)
class JazzAccountRoot
JazzAccountRoot); asyncJazzAccount.migrate(): Promise<void>
migrate() { if (this.JazzAccount.root: co<JazzAccountRoot | null>
root ===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
const organizations: ListOfOrganizations
organizations =class ListOfOrganizations
ListOfOrganizations.
CoList<Item>.create<ListOfOrganizations>(this: CoValueClass<ListOfOrganizations>, items: (Organization | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfOrganizations
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( [ // Create the first Organization so users can start right awayclass Organization
Organization.
CoMap.create<Organization>(this: CoValueClass<...>, init: { name: co<string> & (co<string> | undefined); projects: (ListOfProjects | NonNullable<ListOfProjects & CoMarker>) & (ListOfProjects | ... 1 more ... | undefined); }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): Organization
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( {name: co<string> & (co<string> | undefined)
name: "My organization",projects: (ListOfProjects | NonNullable<ListOfProjects & CoMarker>) & (ListOfProjects | NonNullable<ListOfProjects & CoMarker> | undefined)
projects:class ListOfProjects
ListOfProjects.
CoList<Item>.create<ListOfProjects>(this: CoValueClass<ListOfProjects>, items: (Project | null)[], options?: { owner: Account | Group; } | Account | Group): ListOfProjects
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 organizationGroup: Group
organizationGroup), },const organizationGroup: Group
organizationGroup, ), ], ); this.JazzAccount.root: co<JazzAccountRoot | null>
root =class JazzAccountRoot
JazzAccountRoot.
CoMap.create<JazzAccountRoot>(this: CoValueClass<...>, init: { organizations: (ListOfOrganizations | NonNullable<ListOfOrganizations & CoMarker>) & (ListOfOrganizations | ... 1 more ... | undefined); }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): JazzAccountRoot
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( {organizations: (ListOfOrganizations | NonNullable<ListOfOrganizations & CoMarker>) & (ListOfOrganizations | NonNullable<...> | undefined)
organizations }, ); } } }
This schema now allows users to create Organization
s and add Project
s to them.
See the schema for the example app here.
Adding other users to an Organization
To give users access to an Organization
, you can either send them an invite link, or
add their Account
manually.
Adding users 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: (Organization | null)[] & ListOfOrganizations; } & JazzAccountRoot; } & JazzAccount) | null | undefined
useAccount({
useAccount<JazzAccount, { root: { organizations: { $each: { $onError: null; }; }; }; }>(options?: { resolve?: RefsToResolve<JazzAccount, 10, []> | undefined; } | undefined): { ...; } (+1 overload)
resolve?: RefsToResolve<JazzAccount, 10, []> | undefined
resolve: {root?: RefsToResolve<JazzAccountRoot, 10, [0]> | undefined
root: {organizations?: RefsToResolve<ListOfOrganizations, 10, [0, 0]> | undefined
organizations: {$each: RefsToResolve<Organization, 10, [0, 0, 0]>
$each: {$onError?: null | undefined
$onError: null } } } }, }); constconst onAccept: (organizationId: ID<Organization>) => void
onAccept = (organizationId: ID<Organization>
organizationId:type ID<T> = `co_z${string}` & IDMarker<T>
IDs are unique identifiers for `CoValue`s. Can be used with a type argument to refer to a specific `CoValue` type.ID<class Organization
Organization>) => { if (me) {
const me: ({ root: { organizations: (Organization | null)[] & ListOfOrganizations; } & JazzAccountRoot; } & JazzAccount) | null | undefined
class Organization
Organization.
CoMap.load<Organization, true>(this: CoValueClass<...>, id: ID<Organization>, options?: { resolve?: RefsToResolve<Organization, 10, []> | undefined; loadAs?: Account | AnonymousJazzAgent; } | undefined): Promise<...>
Load a `CoMap` with a given ID, as a given account. `depth` specifies which (if any) fields that reference other CoValues to load as well before resolving. The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth. You can pass `[]` or `{}` for shallowly loading only this CoMap, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues. Check out the `load` methods on `CoMap`/`CoList`/`CoFeed`/`Group`/`Account` to see which depth structures are valid to nest.load(organizationId: ID<Organization>
organizationId).Promise<Organization | null>.then<void, never>(onfulfilled?: ((value: Organization | null) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>
Attaches callbacks for the resolution and/or rejection of the Promise.then((organization: Organization | null
organization) => { if (organization: Organization | null
organization) { // avoid duplicates constconst ids: (ID<Organization> | undefined)[]
ids =me.
const me: { root: { organizations: (Organization | null)[] & ListOfOrganizations; } & JazzAccountRoot; } & JazzAccount
root.
JazzAccount.root: { organizations: (Organization | null)[] & ListOfOrganizations; } & JazzAccountRoot & co<JazzAccountRoot | null>
JazzAccountRoot.organizations: (Organization | null)[] & ListOfOrganizations & co<ListOfOrganizations | null>
organizations.Array<T>.map<ID<Organization> | undefined>(callbackfn: (value: Organization | null, index: number, array: (Organization | null)[]) => ID<Organization> | undefined, thisArg?: any): (ID<...> | 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 | null
organization) =>organization: Organization | null
organization?.CoMap.id: ID<Organization> | undefined
The ID of this `CoMap`id, ); if (const ids: (ID<Organization> | undefined)[]
ids.Array<ID<Organization> | undefined>.includes(searchElement: ID<Organization> | undefined, fromIndex?: number): boolean
Determines whether an array includes a certain element, returning true or false as appropriate.includes(organizationId: ID<Organization>
organizationId)) return;me.
const me: { root: { organizations: (Organization | null)[] & ListOfOrganizations; } & JazzAccountRoot; } & JazzAccount
root.
JazzAccount.root: { organizations: (Organization | null)[] & ListOfOrganizations; } & JazzAccountRoot & co<JazzAccountRoot | null>
JazzAccountRoot.organizations: (Organization | null)[] & ListOfOrganizations & co<ListOfOrganizations | null>
organizations.function push(...items: (Organization | null)[]): number (+1 overload)
Appends new elements to the end of an array, and returns the new length of the array.push(organization: Organization
organization); } }); } };useAcceptInvite({
useAcceptInvite<Organization>({ invitedObjectSchema, onAccept, forValueHint, }: { invitedObjectSchema: CoValueClass<Organization>; onAccept: (projectID: ID<Organization>) => void; forValueHint?: string; }): void
invitedObjectSchema: CoValueClass<Organization>
invitedObjectSchema:class Organization
Organization,onAccept: (projectID: ID<Organization>) => 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>; }
Adding users through their Account ID
...more on this coming soon