Jazz 0.12.0 - Deeply resolved data

Jazz 0.12.0 makes it easier and safer to load nested data. You can now specify exactly which nested data you want to load, and Jazz will check permissions and handle missing data gracefully. This helps catch errors earlier during development and makes your code more reliable.

What's new?

  • New resolve API for a more type-safe deep loading
  • A single, consistent load option for all loading methods
  • Improved permission checks on deep loading
  • Easier type safety with the Resolved type helper

Breaking changes

New Resolve API

We're introducing a new resolve API for deep loading, more friendly to TypeScript, IDE autocompletion and LLMs.

Major changes:

  1. Functions and hooks for loading now take the resolve query as an explicit nested resolve prop
  2. Shallowly loading a collection is now done with true instead of [] or {}
// Before
// @ts-expect-error
const { 
const me: MyAppAccount | ({
    root: AccountRoot | ({
        friends: ListOfAccounts | ((Account | (Account & {
            root: CoMap;
            profile: Profile;
        }))[] & ListOfAccounts);
    } & AccountRoot);
    profile: Profile;
} & MyAppAccount) | null | undefined
me
} =
useAccount<MyAppAccount, RefsToResolve<MyAppAccount>>(options?: {
    resolve?: RefsToResolve<MyAppAccount, 10, []> | undefined;
} | undefined): {
    ...;
} (+1 overload)
useAccount
({
root: {
    friends: never[];
}
root
: { friends: never[]friends: [] } });
// After const {
const me: ({
    root: {
        friends: ListOfAccounts;
    } & AccountRoot;
} & MyAppAccount) | null | undefined
me
} =
useAccount<MyAppAccount, {
    root: {
        friends: true;
    };
}>(options?: {
    resolve?: RefsToResolve<MyAppAccount, 10, []> | undefined;
} | undefined): {
    me: ({
        ...;
    } & MyAppAccount) | ... 1 more ... | undefined;
    logOut: () => void;
} (+1 overload)
useAccount
({
resolve?: RefsToResolve<MyAppAccount, 10, []> | undefinedresolve: { root?: RefsToResolve<AccountRoot, 10, [0]> | undefinedroot: { friends?: RefsToResolve<ListOfAccounts, 10, [0, 0]> | undefinedfriends: true } } });
  1. For collections, resolving items deeply is now done with a special $each key.

For a CoList:

class class TaskTask 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
{ }
class class ListOfTasksListOfTasks 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<Task | null>>(item: co<Task | null>): {
    new (options: {
        fromRaw: RawCoList;
    } | undefined): CoList<co<Task | 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`.
@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 Task>(arg: typeof Task | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof Task), options?: never) => co<...> (+1 overload)
ref
(class TaskTask)) {}
const const id: ID<Task>id = "co_123" as 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.
@example```ts type AccountID = ID<Account>; ```@categoryCoValues
ID
<class TaskTask>;
// Before // @ts-expect-error const const tasks: ListOfTasks | null | undefinedtasks =
useCoState<ListOfTasks, true>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined;
} | undefined): ListOfTasks | ... 1 more ... | undefined
useCoState
(class ListOfTasksListOfTasks, const id: ID<Task>id, [{}]);
// After const const tasks: (Task[] & ListOfTasks) | null | undefinedtasks =
useCoState<ListOfTasks, {
    $each: boolean;
}>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined;
} | undefined): (Task[] & ListOfTasks) | ... 1 more ... | undefined
useCoState
(class ListOfTasksListOfTasks, const id: ID<Task>id, { resolve?: RefsToResolve<ListOfTasks, 10, []> | undefinedresolve: { $each: RefsToResolve<Task, 10, [0]>$each: true } });

For a CoMap.Record:

class class UsersByUsernameUsersByUsername 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
.
CoMap.Record<co<MyAppAccount | null>>(value: co<MyAppAccount | null>): {
    new (options: {
        fromRaw: RawCoMap;
    } | undefined): {
        ...;
    };
    ... 6 more ...;
    fromRaw<V extends CoValue>(this: CoValueClass<V>, raw: import("/vercel/path0/packages/cojson/dist/coValue").RawCoValue): V;
}
Declare a Record-like CoMap schema, by extending `CoMap.Record(...)` and passing the value schema using `co`. Keys are always `string`.
@example```ts import { co, CoMap } from "jazz-tools"; class ColorToFruitMap extends CoMap.Record( co.ref(Fruit) ) {} // assume we have map: ColorToFruitMap // and strawberry: Fruit map["red"] = strawberry; ```@categoryDeclaration
Record
(
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 MyAppAccount>(arg: typeof MyAppAccount | ((_raw: RawAccount<AccountMeta> | RawControlledAccount<AccountMeta>) => typeof MyAppAccount), options?: never) => co<...> (+1 overload)ref(class MyAppAccountMyAppAccount)) {}
// Before // @ts-expect-error const const usersByUsername: UsersByUsername | null | undefinedusersByUsername =
useCoState<UsersByUsername, true>(Schema: CoValueClass<UsersByUsername>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<...> | undefined;
} | undefined): UsersByUsername | ... 1 more ... | undefined
useCoState
(class UsersByUsernameUsersByUsername, const id: ID<UsersByUsername>id, [{}]);
// After const
const usersByUsername: ({
    [key: string]: MyAppAccount;
} & UsersByUsername) | null | undefined
usersByUsername
=
useCoState<UsersByUsername, {
    $each: boolean;
}>(Schema: CoValueClass<UsersByUsername>, id: ID<CoValue> | undefined, options?: {
    ...;
} | undefined): ({
    ...;
} & UsersByUsername) | ... 1 more ... | undefined
useCoState
(class UsersByUsernameUsersByUsername, const id: ID<UsersByUsername>id, {
resolve?: RefsToResolve<UsersByUsername, 10, []> | undefinedresolve: { $each: RefsToResolve<MyAppAccount, 10, [0]>$each: true } });

Nested loading — note how it's now less terse, but more readable:

class class OrgOrg 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
{
Org.name: co<string>name =
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
.string: co<string>string;
} class class AssigneeAssignee 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
{
Assignee.name: co<string>name =
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
.string: co<string>string;
Assignee.org: co<Org | null>org =
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 Org>(arg: typeof Org | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof Org), options?: never) => co<...> (+1 overload)
ref
(class OrgOrg);
} class class ListOfAssigneesListOfAssignees 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<Assignee | null>>(item: co<Assignee | null>): {
    new (options: {
        fromRaw: RawCoList;
    } | undefined): CoList<co<...>>;
    ... 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 Assignee>(arg: typeof Assignee | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof Assignee), options?: never) => co<...> (+1 overload)
ref
(class AssigneeAssignee)) {}
class class TaskTask 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
{
Task.content: co<string>content =
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
.string: co<string>string;
Task.assignees: co<ListOfAssignees | null>assignees =
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 ListOfAssignees>(arg: typeof ListOfAssignees | ((_raw: RawCoList<JsonValue, JsonObject | null>) => typeof ListOfAssignees), options?: never) => co<...> (+1 overload)ref(class ListOfAssigneesListOfAssignees);
} class class ListOfTasksListOfTasks 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<Task | null>>(item: co<Task | null>): {
    new (options: {
        fromRaw: RawCoList;
    } | undefined): CoList<co<Task | 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`.
@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 Task>(arg: typeof Task | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof Task), options?: never) => co<...> (+1 overload)
ref
(class TaskTask)) {}
// Before // @ts-expect-error const const tasksWithAssigneesAndTheirOrgs: ListOfTasks | null | undefinedtasksWithAssigneesAndTheirOrgs =
useCoState<ListOfTasks, true>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined;
} | undefined): ListOfTasks | ... 1 more ... | undefined
useCoState
(class ListOfTasksListOfTasks, const id: ID<ListOfTasks>id, [{
assignees: {
    org: {};
}[]
assignees
: [{ org: {}org: {}}]}
]); // After const
const tasksWithAssigneesAndTheirOrgs: ((Task & {
    assignees: (Assignee & {
        org: Org;
    })[] & ListOfAssignees;
})[] & ListOfTasks) | null | undefined
tasksWithAssigneesAndTheirOrgs
=
useCoState<ListOfTasks, {
    $each: {
        assignees: {
            $each: {
                org: boolean;
            };
        };
    };
}>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: {
    ...;
} | undefined): ((Task & {
    ...;
})[] & ListOfTasks) | ... 1 more ... | undefined
useCoState
(class ListOfTasksListOfTasks, const id: ID<ListOfTasks>id, {
resolve?: RefsToResolve<ListOfTasks, 10, []> | undefinedresolve: { $each: RefsToResolve<Task, 10, [0]>$each: { assignees?: RefsToResolve<ListOfAssignees, 10, [0, 0]> | undefinedassignees: { $each: RefsToResolve<Assignee, 10, [0, 0, 0]>$each: { org?: RefsToResolve<Org, 10, [0, 0, 0, 0]> | undefinedorg: true } } } } });

It's also a lot more auto-complete friendly:

const 
const tasksWithAssigneesAndTheirOrgs: ListOfTasks | ((Task | (Task & {
    assignees: ListOfAssignees | ((Assignee | (Assignee & {
        org: Org;
    }))[] & ListOfAssignees);
}))[] & ListOfTasks) | null | undefined
tasksWithAssigneesAndTheirOrgs
=
useCoState<ListOfTasks, RefsToResolve<ListOfTasks>>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: {
    ...;
} | undefined): ListOfTasks | ... 2 more ... | undefined
useCoState
(class ListOfTasksListOfTasks, const id: ID<ListOfTasks>id, {
resolve?: RefsToResolve<ListOfTasks, 10, []> | undefinedresolve: { $each: RefsToResolve<Task, 10, [0]>$each: { assignees?: RefsToResolve<ListOfAssignees, 10, [0, 0]> | undefinedassignees: { $
  • $each
} } } });

A single, consistent load option

The new API works across all loading methods, and separating out the resolve query means other options with default values are easier to manage, for example: loading a value as a specific account instead of using the implicit current account:

// Before
// @ts-expect-error
class PlaylistPlaylist.
CoMap.load<Playlist, true>(this: CoValueClass<...>, id: ID<Playlist>, options?: {
    resolve?: RefsToResolve<Playlist, 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.
@example```ts const person = await Person.load( "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax", { pet: {} } ); ```@categorySubscription & Loading
load
(const id: ID<Playlist>id, const otherAccount: AccountotherAccount, {
tracks: never[]tracks: [], }); // After class PlaylistPlaylist.
CoMap.load<Playlist, {
    tracks: boolean;
}>(this: CoValueClass<...>, id: ID<Playlist>, options?: {
    resolve?: RefsToResolve<Playlist, 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.
@example```ts const person = await Person.load( "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax", { pet: {} } ); ```@categorySubscription & Loading
load
(const id: ID<Playlist>id, {
loadAs?: Account | AnonymousJazzAgent | undefinedloadAs: const otherAccount: AccountotherAccount, resolve?: RefsToResolve<Playlist, 10, []> | undefinedresolve: { tracks?: RefsToResolve<ListOfTracks, 10, [0]> | undefinedtracks: true } });

Improved permission checks on deep loading

Now useCoState will return null when the current user lacks permissions to load requested data.

Previously, useCoState would return undefined if the current user lacked permissions, making it hard to tell if the value is loading or if it's missing.

Now undefined means that the value is definitely loading, and null means that the value is temporarily missing.

We also have implemented a more granular permission checking, where if an optional CoValue cannot be accessed, useCoState will return the data stripped of that CoValue.

Note: The state handling around loading and error states will become more detailed and easy-to-handle in future releases, so this is just a small step towards consistency.

class class ListOfTracksListOfTracks 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<Track | null | undefined>>(item: co<Track | null | undefined>): {
    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
.
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>;
}
optional
.
ref: <typeof Track>(arg: typeof Track | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof Track)) => co<Track | ... 1 more ... | undefined>
ref
(class TrackTrack)) {}
function
function TrackListComponent({ id }: {
    id: ID<ListOfTracks>;
}): React.JSX.Element | (React.JSX.Element | null | undefined)[]
TrackListComponent
({ id: ID<ListOfTracks>id }: { id: ID<ListOfTracks>id: 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.
@example```ts type AccountID = ID<Account>; ```@categoryCoValues
ID
<class ListOfTracksListOfTracks> }) {
// Before (ambiguous states) // @ts-expect-error const const tracks: ListOfTracks | null | undefinedtracks =
useCoState<ListOfTracks, true>(Schema: CoValueClass<ListOfTracks>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<ListOfTracks, 10, []> | undefined;
} | undefined): ListOfTracks | ... 1 more ... | undefined
useCoState
(class ListOfTracksListOfTracks, id: ID<ListOfTracks>id, [{}]);
if (const tracks: ListOfTracks | null | undefinedtracks === var undefinedundefined) return <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Loading or access denied</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>; if (const tracks: ListOfTracks | nulltracks === null) return <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Not found</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>; // After const const tracks: ListOfTracks | null | undefinedtracks =
useCoState<ListOfTracks, {
    $each: boolean;
}>(Schema: CoValueClass<ListOfTracks>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<...> | undefined;
} | undefined): ListOfTracks | ... 1 more ... | undefined
useCoState
(class ListOfTracksListOfTracks, id: ID<ListOfTracks>id, { resolve?: RefsToResolve<ListOfTracks, 10, []> | undefinedresolve: { $each: RefsToResolve<Track | undefined, 10, [0]>$each: true } });
if (const tracks: ListOfTrackstracks === var undefinedundefined) return <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Loading...</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>; if (const tracks: ListOfTrackstracks === null) return <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Not found or access denied</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>; // This will only show tracks that we have access to and that are loaded. return const tracks: ListOfTrackstracks.Array<co<Track | null | undefined>>.map<React.JSX.Element | null | undefined>(callbackfn: (value: co<Track | null | undefined>, index: number, array: co<Track | null | undefined>[]) => React.JSX.Element | null | undefined, thisArg?: any): (React.JSX.Element | ... 1 more ... | undefined)[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(track: co<Track | null | undefined>track => track: co<Track | null | undefined>track && <
function TrackComponent({ track }: {
    track: Track;
}): string
TrackComponent
track: Tracktrack={track: Track | (Track & CoMarker)track} />);
}

The same change is applied to the load function, so now it returns null instead of undefined when the value is missing.

// Before
// @ts-expect-error
const const map: MyCoMap | nullmap = await class MyCoMapMyCoMap.
CoMap.load<MyCoMap, true>(this: CoValueClass<...>, id: ID<MyCoMap>, options?: {
    resolve?: RefsToResolve<MyCoMap, 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.
@example```ts const person = await Person.load( "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax", { pet: {} } ); ```@categorySubscription & Loading
load
(const id: ID<MyCoMap>id);
if (const map: MyCoMap | nullmap === var undefinedundefined) { throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("Map not found");
} // After const const map: MyCoMap | nullmap = await class MyCoMapMyCoMap.
CoMap.load<MyCoMap, true>(this: CoValueClass<...>, id: ID<MyCoMap>, options?: {
    resolve?: RefsToResolve<MyCoMap, 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.
@example```ts const person = await Person.load( "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax", { pet: {} } ); ```@categorySubscription & Loading
load
(const id: ID<MyCoMap>id);
if (const map: MyCoMap | nullmap === null) { throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("Map not found or access denied");
}

New Features

The Resolved type helper

The new Resolved type can be used to define what kind of deeply loaded data you expect in your parameters, using the same resolve query syntax as the new loading APIs:

type 
type PlaylistResolved = {
    tracks: Track[] & ListOfTracks;
} & Playlist
PlaylistResolved
=
type Resolved<T, R extends RefsToResolve<T> | undefined> = R extends boolean | undefined ? T : [T] extends [(infer Item)[]] ? UnCo<Exclude<Item, null>> extends CoValue ? R extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & UnCo<...> : [...] extends [...] ? UnCo<...> extends CoValue ? ItemDepth extends {
    ...;
} ? (CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue ...
Resolved
<class PlaylistPlaylist, {
tracks: {
    $each: true;
}
tracks
: { $each: true$each: true }
}>; function
function TrackListComponent({ playlist }: {
    playlist: PlaylistResolved;
}): React.JSX.Element[]
TrackListComponent
({
playlist: {
    tracks: Track[] & ListOfTracks;
} & Playlist
playlist
}: {
playlist: {
    tracks: Track[] & ListOfTracks;
} & Playlist
playlist
:
type PlaylistResolved = {
    tracks: Track[] & ListOfTracks;
} & Playlist
PlaylistResolved
}) {
// Safe access to resolved tracks return
playlist: {
    tracks: Track[] & ListOfTracks;
} & Playlist
playlist
.Playlist.tracks: Track[] & ListOfTracks & co<ListOfTracks | null>tracks.Array<T>.map<React.JSX.Element>(callbackfn: (value: Track, index: number, array: Track[]) => 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.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(track: Tracktrack => <
function TrackComponent({ track }: {
    track: Track;
}): string
TrackComponent
track: Tracktrack={track: Tracktrack} />);
}