Public sharing and invites
Public sharing
You can share CoValues publicly by setting the owner
to a Group
, and granting
access to "everyone".
const group = Group.create(); group.addMember("everyone", "writer"); // *highlight*
This is done in the chat example where anyone can join the chat, and send messages.
You can also add members by Account ID.
Invites
You can grant users access to a CoValue by sending them an invite link.
This is used in the pet example and the todo example.
import { createInviteLink } from "jazz-vue"; createInviteLink(organization, "writer"); // or reader, or admin
It generates a URL that looks like .../invite/[CoValue ID]/[inviteSecret]
In your app, you need to handle this route, and let the user accept the invitation, as done here.
useAcceptInvite({ invitedObjectSchema: PetPost, onAccept: (petPostID) => navigate("/pet/" + petPostID), });
Requesting Invites
To allow a non-group member to request an invitation to a group you can use the writeOnly
role.
This means that users only have write access to a specific requests list (they can't read other requests).
However, Administrators can review and approve these requests.
Create the data models.
class
class JoinRequest
JoinRequest extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {JoinRequest.account: co<Account | null>
account =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 Account>(arg: typeof Account | ((_raw: RawAccount<AccountMeta>) => typeof Account), options?: never) => co<Account | null> (+1 overload)
ref(class Account
Account);JoinRequest.status: co<"pending" | "approved" | "rejected">
status =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>; }; }
literal<["pending", "approved", "rejected"]>(_lit_0: "pending", _lit_1: "approved", _lit_2: "rejected"): co<"pending" | "approved" | "rejected">
literal("pending", "approved", "rejected"); } classclass RequestsList
RequestsList extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.CoList.
CoList<Item = any>.Of<co<JoinRequest | null>>(item: co<JoinRequest | 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 JoinRequest>(arg: typeof JoinRequest | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof JoinRequest), options?: never) => co<...> (+1 overload)
class JoinRequest
JoinRequest)) {};
Set up the request system with appropriate access controls.
function
function createRequestsToJoin(): RequestsList
createRequestsToJoin() { constconst requestsGroup: Group
requestsGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const requestsGroup: Group
requestsGroup.Group.addMember(member: Everyone, role: "writer" | "reader" | "writeOnly"): void (+1 overload)
addMember("everyone", "writeOnly"); returnclass RequestsList
RequestsList.
CoList<Item>.create<RequestsList>(this: CoValueClass<RequestsList>, items: (JoinRequest | null)[], options?: { owner: Account | Group; } | Account | Group): RequestsList
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 requestsGroup: Group
requestsGroup); } async functionfunction sendJoinRequest(requestsList: RequestsList, account: Account): Promise<JoinRequest>
sendJoinRequest(requestsList: RequestsList
requestsList:class RequestsList
RequestsList,account: Account
account:class Account
Account, ) { constconst request: JoinRequest
request =class JoinRequest
JoinRequest.
CoMap.create<JoinRequest>(this: CoValueClass<...>, init: { account: (Account | NonNullable<Account & CoMarker>) & (Account | NonNullable<Account & CoMarker> | undefined); status: co<...> & (co<...> | undefined); }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): JoinRequest
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( {account: (Account | NonNullable<Account & CoMarker>) & (Account | NonNullable<Account & CoMarker> | undefined)
account,status: co<"pending" | "approved" | "rejected"> & (co<"pending" | "approved" | "rejected"> | undefined)
status: "pending", },requestsList: RequestsList
requestsList.CoList<co<JoinRequest | null>>._owner: Account | Group
_owner // Inherit the access controls of the requestsList );requestsList: RequestsList
requestsList.CoList<co<JoinRequest | null>>.push(...items: co<JoinRequest | null>[]): number
Appends new elements to the end of an array, and returns the new length of the array.push(const request: JoinRequest
request); returnconst request: JoinRequest
request; }
Using the write-only access users can submit requests that only administrators can review and approve.
async function
function approveJoinRequest(joinRequest: JoinRequest, targetGroup: Group): Promise<boolean>
approveJoinRequest(joinRequest: JoinRequest
joinRequest:class JoinRequest
JoinRequest,targetGroup: Group
targetGroup:class Group
Group, ) { constconst account: Account | null
account = awaitclass Account
Account.load(
Account.load<Account, true>(this: CoValueClass<...>, id: ID<Account>, options?: { resolve?: RefsToResolve<Account, 10, []> | undefined; loadAs?: Account | AnonymousJazzAgent; } | undefined): Promise<...>
joinRequest: JoinRequest
joinRequest.
CoMap._refs: { account: Ref<Account>; status: never; }
If property `prop` is a `co.ref(...)`, you can use `coMaps._refs.prop` to access the `Ref` instead of the potentially loaded/null value. This allows you to always get the ID or load the value manually._refs.account: Ref<Account>
account.Ref<Account>.id: ID<Account>
id); if (const account: Account | null
account) {targetGroup: Group
targetGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(const account: Account
account, "reader");joinRequest: JoinRequest
joinRequest.JoinRequest.status: co<string>
status = "approved"; return true; } else { return false; } }