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.

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 JoinRequestJoinRequest extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
JoinRequest.account: co<Account | null>account =
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>;
    };
}
@categorySchema definition@categorySchema definition
co
.ref: <typeof Account>(arg: typeof Account | ((_raw: RawAccount<AccountMeta>) => typeof Account), options?: never) => co<Account | null> (+1 overload)ref(class Account
@categoryIdentity & Permissions
Account
);
JoinRequest.status: co<"pending" | "approved" | "rejected">status =
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>;
    };
}
@categorySchema definition@categorySchema definition
co
.literal<["pending", "approved", "rejected"]>(_lit_0: "pending", _lit_1: "approved", _lit_2: "rejected"): co<"pending" | "approved" | "rejected">literal("pending", "approved", "rejected");
} class class RequestsListRequestsList extends class CoList<Item = any>
CoLists are collaborative versions of plain arrays.
@categoryDescriptionContent You can access items on a `CoList` as if they were normal items on a plain array, using `[]` notation, etc. Since `CoList` is a subclass of `Array`, you can use all the normal array methods like `push`, `pop`, `splice`, etc. ```ts colorList[0]; colorList[3] = "yellow"; colorList.push("Kawazaki Green"); colorList.splice(1, 1); ```@categoryCoValues
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`.
@example```ts class ColorList extends CoList.Of( co.string ) {} class AnimalList extends CoList.Of( co.ref(Animal) ) {} ```@categoryDeclaration
Of
(
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>;
    };
}
@categorySchema definition@categorySchema definition
co
.
ref: <typeof JoinRequest>(arg: typeof JoinRequest | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof JoinRequest), options?: never) => co<...> (+1 overload)
ref
(class JoinRequestJoinRequest)) {};

Set up the request system with appropriate access controls.

function function createRequestsToJoin(): RequestsListcreateRequestsToJoin() {
  const const requestsGroup: GrouprequestsGroup = class Group
@categoryIdentity & Permissions
Group
.
Group.create<Group>(this: CoValueClass<Group>, options?: {
    owner: Account;
} | Account): Group
create
();
const requestsGroup: GrouprequestsGroup.Group.addMember(member: Everyone, role: "writer" | "reader" | "writeOnly"): void (+1 overload)addMember("everyone", "writeOnly"); return class RequestsListRequestsList.
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.
@example```ts const colours = ColorList.create( ["red", "green", "blue"], { owner: me } ); const animals = AnimalList.create( [cat, dog, fish], { owner: me } ); ```@categoryCreation
create
([], const requestsGroup: GrouprequestsGroup);
} async function function sendJoinRequest(requestsList: RequestsList, account: Account): Promise<JoinRequest>sendJoinRequest( requestsList: RequestsListrequestsList: class RequestsListRequestsList, account: Accountaccount: class Account
@categoryIdentity & Permissions
Account
,
) { const const request: JoinRequestrequest = class JoinRequestJoinRequest.
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.
@example```ts const person = Person.create({ name: "Alice", age: 42, pet: cat, }, { owner: friendGroup }); ```@categoryCreation
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: RequestsListrequestsList.CoList<co<JoinRequest | null>>._owner: Account | Group
@categoryCollaboration
_owner
// Inherit the access controls of the requestsList
); requestsList: RequestsListrequestsList.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.
@paramitems New elements to add to the array.
push
(const request: JoinRequestrequest);
return const request: JoinRequestrequest; }

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: JoinRequestjoinRequest: class JoinRequestJoinRequest,
  targetGroup: GrouptargetGroup: class Group
@categoryIdentity & Permissions
Group
,
) { const const account: Account | nullaccount = await class Account
@categoryIdentity & Permissions
Account
.
Account.load<Account, true>(this: CoValueClass<...>, id: ID<Account>, options?: {
    resolve?: RefsToResolve<Account, 10, []> | undefined;
    loadAs?: Account | AnonymousJazzAgent;
} | undefined): Promise<...>
@categorySubscription & Loading
load
(joinRequest: JoinRequestjoinRequest.
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.
@example```ts person._refs.pet.id; // => ID<Animal> person._refs.pet.value; // => Animal | null const pet = await person._refs.pet.load(); ```@categoryContent
_refs
.account: Ref<Account>account.Ref<Account>.id: ID<Account>id);
if (const account: Account | nullaccount) { targetGroup: GrouptargetGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)addMember(const account: Accountaccount, "reader"); joinRequest: JoinRequestjoinRequest.JoinRequest.status: co<string>status = "approved"; return true; } else { return false; } }