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:
- Functions and hooks for loading now take the resolve query as an explicit nested
resolve
prop - Shallowly loading a collection is now done with
true
instead of[]
or{}
// Before // @ts-expect-error const {
me } =
const me: MyAppAccount | ({ root: AccountRoot | ({ friends: ListOfAccounts | ((Account | (Account & { root: CoMap; profile: Profile; }))[] & ListOfAccounts); } & AccountRoot); profile: Profile; } & MyAppAccount) | null | undefined
useAccount({
useAccount<MyAppAccount, RefsToResolve<MyAppAccount>>(options?: { resolve?: RefsToResolve<MyAppAccount, 10, []> | undefined; } | undefined): { ...; } (+1 overload)
root: {
root: { friends: never[]; }
friends: never[]
friends: [] } }); // After const {me } =
const me: ({ root: { friends: ListOfAccounts; } & AccountRoot; } & MyAppAccount) | null | undefined
useAccount({
useAccount<MyAppAccount, { root: { friends: true; }; }>(options?: { resolve?: RefsToResolve<MyAppAccount, 10, []> | undefined; } | undefined): { me: ({ ...; } & MyAppAccount) | ... 1 more ... | undefined; logOut: () => void; } (+1 overload)
resolve?: RefsToResolve<MyAppAccount, 10, []> | undefined
resolve: {root?: RefsToResolve<AccountRoot, 10, [0]> | undefined
root: {friends?: RefsToResolve<ListOfAccounts, 10, [0, 0]> | undefined
friends: true } } });
- For collections, resolving items deeply is now done with a special
$each
key.
For a CoList
:
class
class Task
Task extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap { } classclass ListOfTasks
ListOfTasks extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.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`.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 Task>(arg: typeof Task | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Task), options?: never) => co<...> (+1 overload)
class Task
Task)) {} constconst id: ID<Task>
id = "co_123" astype 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 Task
Task>; // Before // @ts-expect-error constconst tasks: ListOfTasks | null | undefined
tasks =useCoState(
useCoState<ListOfTasks, true>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: { resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined; } | undefined): ListOfTasks | ... 1 more ... | undefined
class ListOfTasks
ListOfTasks,const id: ID<Task>
id, [{}]); // After constconst tasks: (Task[] & ListOfTasks) | null | undefined
tasks =useCoState(
useCoState<ListOfTasks, { $each: boolean; }>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: { resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined; } | undefined): (Task[] & ListOfTasks) | ... 1 more ... | undefined
class ListOfTasks
ListOfTasks,const id: ID<Task>
id, {resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined
resolve: {$each: RefsToResolve<Task, 10, [0]>
$each: true } });
For a CoMap.Record
:
class
class UsersByUsername
UsersByUsername extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.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`.Record(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 MyAppAccount>(arg: typeof MyAppAccount | ((_raw: RawAccount<AccountMeta> | RawControlledAccount<AccountMeta>) => typeof MyAppAccount), options?: never) => co<...> (+1 overload)
ref(class MyAppAccount
MyAppAccount)) {} // Before // @ts-expect-error constconst usersByUsername: UsersByUsername | null | undefined
usersByUsername =useCoState(
useCoState<UsersByUsername, true>(Schema: CoValueClass<UsersByUsername>, id: ID<CoValue> | undefined, options?: { resolve?: RefsToResolve<...> | undefined; } | undefined): UsersByUsername | ... 1 more ... | undefined
class UsersByUsername
UsersByUsername,const id: ID<UsersByUsername>
id, [{}]); // After constusersByUsername =
const usersByUsername: ({ [key: string]: MyAppAccount; } & UsersByUsername) | null | undefined
useCoState(
useCoState<UsersByUsername, { $each: boolean; }>(Schema: CoValueClass<UsersByUsername>, id: ID<CoValue> | undefined, options?: { ...; } | undefined): ({ ...; } & UsersByUsername) | ... 1 more ... | undefined
class UsersByUsername
UsersByUsername,const id: ID<UsersByUsername>
id, {resolve?: RefsToResolve<UsersByUsername, 10, []> | undefined
resolve: {$each: RefsToResolve<MyAppAccount, 10, [0]>
$each: true } });
Nested loading — note how it's now less terse, but more readable:
class
class Org
Org extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {Org.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; } classclass Assignee
Assignee extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {Assignee.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;Assignee.org: co<Org | null>
org =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 Org>(arg: typeof Org | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Org), options?: never) => co<...> (+1 overload)
class Org
Org); } classclass ListOfAssignees
ListOfAssignees extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.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`.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 Assignee>(arg: typeof Assignee | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Assignee), options?: never) => co<...> (+1 overload)
class Assignee
Assignee)) {} classclass Task
Task extendsclass CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.CoMap {Task.content: co<string>
content =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;Task.assignees: co<ListOfAssignees | null>
assignees =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 ListOfAssignees>(arg: typeof ListOfAssignees | ((_raw: RawCoList<JsonValue, JsonObject | null>) => typeof ListOfAssignees), options?: never) => co<...> (+1 overload)
ref(class ListOfAssignees
ListOfAssignees); } classclass ListOfTasks
ListOfTasks extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.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`.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 Task>(arg: typeof Task | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Task), options?: never) => co<...> (+1 overload)
class Task
Task)) {} // Before // @ts-expect-error constconst tasksWithAssigneesAndTheirOrgs: ListOfTasks | null | undefined
tasksWithAssigneesAndTheirOrgs =useCoState(
useCoState<ListOfTasks, true>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: { resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined; } | undefined): ListOfTasks | ... 1 more ... | undefined
class ListOfTasks
ListOfTasks,const id: ID<ListOfTasks>
id, [{assignees: [{
assignees: { org: {}; }[]
org: {}
org: {}}]} ]); // After consttasksWithAssigneesAndTheirOrgs =
const tasksWithAssigneesAndTheirOrgs: ((Task & { assignees: (Assignee & { org: Org; })[] & ListOfAssignees; })[] & ListOfTasks) | null | undefined
useCoState(
useCoState<ListOfTasks, { $each: { assignees: { $each: { org: boolean; }; }; }; }>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: { ...; } | undefined): ((Task & { ...; })[] & ListOfTasks) | ... 1 more ... | undefined
class ListOfTasks
ListOfTasks,const id: ID<ListOfTasks>
id, {resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined
resolve: {$each: RefsToResolve<Task, 10, [0]>
$each: {assignees?: RefsToResolve<ListOfAssignees, 10, [0, 0]> | undefined
assignees: {$each: RefsToResolve<Assignee, 10, [0, 0, 0]>
$each: {org?: RefsToResolve<Org, 10, [0, 0, 0, 0]> | undefined
org: true } } } } });
It's also a lot more auto-complete friendly:
const
tasksWithAssigneesAndTheirOrgs =
const tasksWithAssigneesAndTheirOrgs: ListOfTasks | ((Task | (Task & { assignees: ListOfAssignees | ((Assignee | (Assignee & { org: Org; }))[] & ListOfAssignees); }))[] & ListOfTasks) | null | undefined
useCoState(
useCoState<ListOfTasks, RefsToResolve<ListOfTasks>>(Schema: CoValueClass<ListOfTasks>, id: ID<CoValue> | undefined, options?: { ...; } | undefined): ListOfTasks | ... 2 more ... | undefined
class ListOfTasks
ListOfTasks,const id: ID<ListOfTasks>
id, {resolve?: RefsToResolve<ListOfTasks, 10, []> | undefined
resolve: {$each: RefsToResolve<Task, 10, [0]>
$each: {assignees?: RefsToResolve<ListOfAssignees, 10, [0, 0]> | undefined
assignees: { $} } } });
- $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 Playlist
Playlist.
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.load(const id: ID<Playlist>
id,const otherAccount: Account
otherAccount, {tracks: never[]
tracks: [], }); // Afterclass Playlist
Playlist.
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.load(const id: ID<Playlist>
id, {loadAs?: Account | AnonymousJazzAgent | undefined
loadAs:const otherAccount: Account
otherAccount,resolve?: RefsToResolve<Playlist, 10, []> | undefined
resolve: {tracks?: RefsToResolve<ListOfTracks, 10, [0]> | undefined
tracks: 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 ListOfTracks
ListOfTracks extendsclass CoList<Item = any>
CoLists are collaborative versions of plain arrays.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`.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>; }; }
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 Track>(arg: typeof Track | ((_raw: RawCoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>) => typeof Track)) => co<Track | ... 1 more ... | undefined>
class Track
Track)) {} functionTrackListComponent({
function TrackListComponent({ id }: { id: ID<ListOfTracks>; }): React.JSX.Element | (React.JSX.Element | null | undefined)[]
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.ID<class ListOfTracks
ListOfTracks> }) { // Before (ambiguous states) // @ts-expect-error constconst tracks: ListOfTracks | null | undefined
tracks =useCoState(
useCoState<ListOfTracks, true>(Schema: CoValueClass<ListOfTracks>, id: ID<CoValue> | undefined, options?: { resolve?: RefsToResolve<ListOfTracks, 10, []> | undefined; } | undefined): ListOfTracks | ... 1 more ... | undefined
class ListOfTracks
ListOfTracks,id: ID<ListOfTracks>
id, [{}]); if (const tracks: ListOfTracks | null | undefined
tracks ===var undefined
undefined) 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 | null
tracks === 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 constconst tracks: ListOfTracks | null | undefined
tracks =useCoState(
useCoState<ListOfTracks, { $each: boolean; }>(Schema: CoValueClass<ListOfTracks>, id: ID<CoValue> | undefined, options?: { resolve?: RefsToResolve<...> | undefined; } | undefined): ListOfTracks | ... 1 more ... | undefined
class ListOfTracks
ListOfTracks,id: ID<ListOfTracks>
id, {resolve?: RefsToResolve<ListOfTracks, 10, []> | undefined
resolve: {$each: RefsToResolve<Track | undefined, 10, [0]>
$each: true } }); if (const tracks: ListOfTracks
tracks ===var undefined
undefined) 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: ListOfTracks
tracks === 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. returnconst tracks: ListOfTracks
tracks.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.map(track: co<Track | null | undefined>
track =>track: co<Track | null | undefined>
track && <TrackComponent
function TrackComponent({ track }: { track: Track; }): string
track: Track
track={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 | null
map = awaitclass MyCoMap
MyCoMap.
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.load(const id: ID<MyCoMap>
id); if (const map: MyCoMap | null
map ===var undefined
undefined) { throw newError("Map not found"); } // After const
var Error: ErrorConstructor new (message?: string, options?: ErrorOptions) => Error (+1 overload)
const map: MyCoMap | null
map = awaitclass MyCoMap
MyCoMap.
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.load(const id: ID<MyCoMap>
id); if (const map: MyCoMap | null
map === null) { throw newError("Map not found or access denied"); }
var Error: ErrorConstructor new (message?: string, options?: ErrorOptions) => Error (+1 overload)
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
PlaylistResolved =
type PlaylistResolved = { tracks: Track[] & ListOfTracks; } & Playlist
Resolved<
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 ...
class Playlist
Playlist, {tracks: {
tracks: { $each: true; }
$each: true
$each: true } }>; functionTrackListComponent({
function TrackListComponent({ playlist }: { playlist: PlaylistResolved; }): React.JSX.Element[]
playlist }: {
playlist: { tracks: Track[] & ListOfTracks; } & Playlist
playlist:
playlist: { tracks: Track[] & ListOfTracks; } & Playlist
PlaylistResolved }) { // Safe access to resolved tracks return
type PlaylistResolved = { tracks: Track[] & ListOfTracks; } & Playlist
playlist.
playlist: { tracks: Track[] & ListOfTracks; } & 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.map(track: Track
track => <TrackComponent
function TrackComponent({ track }: { track: Track; }): string
track: Track
track={track: Track
track} />); }