Jazz 0.14.0 Introducing Zod-based schemas
We're excited to move from our own schema syntax to using Zod v4.
This is the first step in a series of releases to make Jazz more familiar and to make CoValues look more like regular data structures.
Note: This is a huge release that we're still cleaning up and documenting.
We're still in the process of:
- updating all our docs
- double-checking all our framework bindings
- completing all the details of this upgrade guide
If you see something broken, please let us know on Discord and check back in a couple hours.
Thanks for your patience!
Overview:
So far, Jazz has relied on our own idiosyncratic schema definition syntax where you had to extend classes and be careful to use co.ref
for references.
// BEFORE import { co, CoMap, CoList, CoPlainText, ImageDefinition } from "jazz-tools"; export class Message extends CoMap { text = co.ref(CoPlainText); image = co.optional.ref(ImageDefinition); important = co.boolean; } export class Chat extends CoList.Of(co.ref(Message)) {}
While this had certain ergonomic benefits it relied on unclean hacks to work.
In addition, many of our adopters expressed a preference for avoiding class syntax, and LLMs consistently expected to be able to use Zod.
For this reason, we completely overhauled how you define and use CoValue schemas:
// AFTER import {
import co
co,import z
z } from "jazz-tools"; export constMessage =
const Message: CoMapSchema<{ text: PlainTextSchema; image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>; important: z.z.ZodBoolean; }>
import co
co.map({
map<{ text: PlainTextSchema; image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>; important: z.z.ZodBoolean; }>(shape: { text: PlainTextSchema; image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>; important: z.z.ZodBoolean; }): CoMapSchema<...> export map
text: PlainTextSchema
text:import co
co.plainText(),
function plainText(): PlainTextSchema export plainText
image:
image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>
import z
z.optional(
optional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>(innerType: WithHelpers<...>): z.ZodOptional<...> export optional
import co
co.image()),
function image(): typeof ImageDefinition export image
important: z.z.ZodBoolean
important:import z
z.boolean(), }); export const
function boolean(params?: string | z.z.core.$ZodBooleanParams): z.z.ZodBoolean export boolean
Chat =
const Chat: CoListSchema<CoMapSchema<{ text: PlainTextSchema; image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>; important: z.z.ZodBoolean; }>>
import co
co.list(
list<CoMapSchema<{ text: PlainTextSchema; image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>; important: z.z.ZodBoolean; }>>(element: CoMapSchema<...>): CoListSchema<...> export list
Message);
const Message: CoMapSchema<{ text: PlainTextSchema; image: z.ZodOptional<WithHelpers<CoMapSchema<{ originalSize: z.z.ZodTuple<[z.z.ZodNumber, z.z.ZodNumber], null>; placeholderDataURL: z.ZodOptional<z.z.ZodString>; }, z.z.core.$catchall<...>>, { ...; }>>; important: z.z.ZodBoolean; }>
Major breaking changes
Schema definitions
You now define CoValue schemas using two new exports from jazz-tools
:
- a new
co
definer that mirrors Zod's object/record/array syntax to define CoValue typesco.map()
,co.record()
,co.list()
,co.feed()
co.account()
,co.profile()
co.plainText()
,co.richText()
,co.fileStream()
,co.image()
- see the updated Defining CoValue Schemas
z
re-exported from Zod v4- primitives like
z.string()
,z.number()
,z.literal()
- note: additional constraints like
z.min()
andz.max()
are not yet enforced, we'll add validation in future releases
- note: additional constraints like
- complex types like
z.object()
andz.array()
to define JSON-like fields without internal collaboration - combinators like
z.optional()
andz.discriminatedUnion()
- these also work on CoValue types!
- see the updated Docs on Primitive Fields, Docs on Optional References and Docs on Unions of CoMaps
- primitives like
Similar to Zod v4's new object syntax, recursive and mutually recursive types are now much easier to express.
How to pass loaded CoValues
Calls to useCoState()
work just the same, but they return a slightly different type than before.
And while you can still read from the type just as before...
import {
import z
z,import co
co } from "jazz-tools"; import {useCoState } from "jazz-react"; const
function useCoState<S extends CoValueOrZodSchema, const R extends ResolveQuery<S> = true>(Schema: S, id: string | undefined, options?: { resolve?: ResolveQueryStrict<S, R>; }): co.loaded<S, R> | undefined | null
Pet =
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
import co
co.map({
map<{ name: z.z.ZodString; age: z.z.ZodNumber; }>(shape: { name: z.z.ZodString; age: z.z.ZodNumber; }): CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }> export map
name: z.z.ZodString
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.z.ZodNumber
age:import z
z.number(), }); type
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
Pet =
type Pet = { name: string; age: number; } & CoMap
import co
co.loaded<typeof
type loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<... export loaded
Pet>; const
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
Person =
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
import co
co.map({
map<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>(shape: { name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }): CoMapSchema<...> export map
name: z.z.ZodString
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.z.ZodNumber
age:import z
z.number(),
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
pets:
pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>
import co
co.list(
list<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>(element: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>): CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>> export list
Pet), }); type
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
Person =
type Person = { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
import co
co.loaded<typeof
type loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<... export loaded
Person>; function
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
MyComponent({
function MyComponent({ id }: { id: string; }): React.JSX.Element | null | undefined
id: string
id }: {id: string
id: string }) { constperson =
const person: ({ name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap) | null | undefined
useCoState(
useCoState<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>, true>(Schema: CoMapSchema<...>, id: string | undefined, options?: { ...; } | undefined): ({ ...; } & CoMap) | ... 1 more ... | undefined
Person,
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
id: string
id); returnperson && <
const person: ({ name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap) | null | undefined
PersonName
function PersonName({ person }: { person: Person; }): React.JSX.Element
person={
person: { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
person} />; } function
const person: { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
PersonName({
function PersonName({ person }: { person: Person; }): React.JSX.Element
person }: {
person: { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
person:
person: { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
Person }) { return <
type Person = { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{person.
person: { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
name: string
name}</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>; }
co.loaded
can also take a second argument to specify the loading depth of the expected CoValue, mirroring the resolve
options for useCoState
, load
, subscribe
, etc.
import {
import z
z,import co
co } from "jazz-tools"; import {useCoState } from "jazz-react"; const
function useCoState<S extends CoValueOrZodSchema, const R extends ResolveQuery<S> = true>(Schema: S, id: string | undefined, options?: { resolve?: ResolveQueryStrict<S, R>; }): co.loaded<S, R> | undefined | null
Pet =
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
import co
co.map({
map<{ name: z.z.ZodString; age: z.z.ZodNumber; }>(shape: { name: z.z.ZodString; age: z.z.ZodNumber; }): CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }> export map
name: z.z.ZodString
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.z.ZodNumber
age:import z
z.number(), }); type
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
Pet =
type Pet = { name: string; age: number; } & CoMap
import co
co.loaded<typeof
type loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<... export loaded
Pet>; const
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
Person =
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
import co
co.map({
map<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>(shape: { name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }): CoMapSchema<...> export map
name: z.z.ZodString
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.z.ZodNumber
age:import z
z.number(),
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
pets:
pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>
import co
co.list(
list<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>(element: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>): CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>> export list
Pet), }); type
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
Person =
type Person = { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
import co
co.loaded<typeof
type loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<... export loaded
Person>; function
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
MyComponent({
function MyComponent({ id }: { id: string; }): React.JSX.Element | null | undefined
id: string
id }: {id: string
id: string }) { constpersonWithPets =
const personWithPets: ({ pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap) | null | undefined
useCoState(
useCoState<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>, { ...; }>(Schema: CoMapSchema<...>, id: string | undefined, options?: { ...; } | undefined): ({ ...; } & ... 1 more ... & CoMap) | ... 1 more ... | undefined
Person,
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
id: string
id, {resolve: {
resolve?: RefsToResolve<{ name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap, 10, []> | undefined
pets: {
pets?: RefsToResolve<CoList<({ name: string; age: number; } & CoMap) | null>, 10, [0]> | undefined
$each: true } } }); return
$each: RefsToResolve<{ name: string; age: number; } & CoMap, 10, [0, 0]>
personWithPets && <
const personWithPets: ({ pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap) | null | undefined
PersonAndFirstPetName
function PersonAndFirstPetName({ person }: { person: co.loaded<typeof Person, { pets: { $each: true; }; }>; }): React.JSX.Element
person={
person: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
personWithPets} />; } function
const personWithPets: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
PersonAndFirstPetName({
function PersonAndFirstPetName({ person }: { person: co.loaded<typeof Person, { pets: { $each: true; }; }>; }): React.JSX.Element
person }: {
person: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
person:
person: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
import co
co.loaded<typeof
type loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<... export loaded
Person, {
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
pets: {
pets: { $each: true; }
$each: true
$each: true } }> }) { return <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{person.
person: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
name: string
name} & {person.
person: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>; } & { name: string; age: number; pets: CoList<({ name: string; age: number; } & CoMap) | null> | null; } & CoMap
pets[0].
pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>
name: string
name}</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>; }
Removing AccountSchema registration
We have removed the Typescript AccountSchema registration.
It was causing some deal of confusion to new adopters so we have decided to replace the magic inference with a more explicit approach.
When using useAccount
you should now pass the Account
schema directly:
import {
useAccount } from "jazz-react"; import {
function useAccount<A extends AccountClass<Account> | AnyAccountSchema>(AccountSchema?: A): { me: Loaded<A, true>; logOut: () => void; } (+1 overload)
MyAccount } from "./schema"; function
const MyAccount: AccountSchema<{ profile: CoProfileSchema<{ name: $ZodString<string>; inbox?: $ZodOptional<$ZodString>; inboxInvite?: $ZodOptional<$ZodString>; }>; root: CoMapSchema<...>; }>
function MyComponent(): React.JSX.Element
MyComponent() { const {me } =
const me: ({ profile: { name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile; } & { profile: ({ name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap) | null; root: ({} & CoMap) | null; } & Account) | null | undefined
useAccount(
useAccount<AccountSchema<{ profile: CoProfileSchema<{ name: $ZodString<string>; inbox?: $ZodOptional<$ZodString>; inboxInvite?: $ZodOptional<$ZodString>; }>; root: CoMapSchema<...>; }>, { ...; }>(AccountSchema: AccountSchema<...>, options?: { ...; } | undefined): { ...; } (+1 overload)
MyAccount, {
const MyAccount: AccountSchema<{ profile: CoProfileSchema<{ name: $ZodString<string>; inbox?: $ZodOptional<$ZodString>; inboxInvite?: $ZodOptional<$ZodString>; }>; root: CoMapSchema<...>; }>
resolve: {
resolve?: RefsToResolve<{ profile: ({ name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap) | null; root: ({} & CoMap) | null; } & Account, 10, []> | undefined
profile: true, }, }); return <
profile?: RefsToResolve<{ name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile, 10, [0]> | undefined
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{me?.
const me: ({ profile: { name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile; } & { profile: ({ name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap) | null; root: ({} & CoMap) | null; } & Account) | null | undefined
profile.
Account.profile: { name: string; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile
Profile.name: string | undefined
name}</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>; }
Defining migrations
Now account schemas need to be defined with co.account()
and migrations can be declared using withMigration()
:
import {
import co
co,import z
z,class Group
Group } from "jazz-tools"; constPet =
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
import co
co.map({
map<{ name: z.z.ZodString; age: z.z.ZodNumber; }>(shape: { name: z.z.ZodString; age: z.z.ZodNumber; }): CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }> export map
name: z.z.ZodString
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.z.ZodNumber
age:import z
z.number(), }); const
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
MyAppRoot =
const MyAppRoot: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
import co
co.map({
map<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>(shape: { pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }): CoMapSchema<...> export map
pets:
pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>
import co
co.list(
list<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>(element: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>): CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>> export list
Pet), }); const
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
MyAppProfile =
const MyAppProfile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<z.z.ZodNumber>; }>
import co
co.profile({
profile<{ name: z.z.ZodString; age: z.ZodOptional<z.z.ZodNumber>; }>(shape?: ({ name: z.z.ZodString; age: z.ZodOptional<z.z.ZodNumber>; } & { name?: z.z.core.$ZodString<string>; inbox?: z.z.core.$ZodOptional<z.z.core.$ZodString>; inboxInvite?: z.z.core.$ZodOptional<z.z.core.$ZodString>; }) | undefined): CoProfileSchema<...> export profile
name: z.z.ZodString & z.z.core.$ZodString<string>
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.ZodOptional<z.z.ZodNumber>
age:import z
z.number().
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
ZodType<unknown, unknown>.optional(): z.ZodOptional<z.z.ZodNumber>
optional(), }); export constMyAppAccount =
const MyAppAccount: AccountSchema<{ root: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>; profile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<...>; }>; }>
import co
co.account({
account<{ root: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>; profile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<...>; }>; }>(shape?: { root: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>; profile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<...>; }>; } | undefined): AccountSchema<...> export account
root:
root: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
MyAppRoot,
const MyAppRoot: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
profile:
profile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<z.z.ZodNumber>; }>
MyAppProfile, }).
const MyAppProfile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<z.z.ZodNumber>; }>
withMigration((
function withMigration(migration: (account: { root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap; profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account, creationProps?: { name: string; }) => void): AccountSchema<...>
account,
account: { root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap; profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
creationProps?: {
creationProps: { name: string; } | undefined
name: string
name: string }) => { if (account.
account: { root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap; profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
root ===
Account.root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap
var undefined
undefined) {account.
account: { root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap; profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
root =
Account.root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap
MyAppRoot.
const MyAppRoot: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>
create({
create: (init: { pets: CoList<({ name: string; age: number; } & CoMap) | null>; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
pets:
pets: CoList<({ name: string; age: number; } & CoMap) | null>
import co
co.list(
list<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>(element: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>): CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>> export list
Pet).
const Pet: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
create([]), }); } if (
create: (items: CoListInit<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>, options?: { owner: Account | Group; } | Account | Group) => CoList<...>
account.
account: { root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap; profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
profile ===
profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile
var undefined
undefined) { constconst profileGroup: Group
profileGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const profileGroup: Group
profileGroup.Group.addMember(member: Everyone, role: "writer" | "reader" | "writeOnly"): void (+1 overload)
addMember("everyone", "reader");account.
account: { root: { pets: CoList<{ name: string; age: number; } & CoMap>; } & CoMap; profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap; } & { ...; } & Account
profile =
profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile
MyAppProfile.
const MyAppProfile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<z.z.ZodNumber>; }>
create({
create: (init: { age?: number | undefined; inbox?: string | undefined; inboxInvite?: string | undefined; name: string; }, options: { owner: Exclude<Group, Account>; } | Exclude<Group, Account>) => CoMapInstance<...>
name: string
name:creationProps?.
creationProps: { name: string; } | undefined
name: string | undefined
name ?? "New user", },const profileGroup: Group
profileGroup); } });
Defining Schema helper methods
TODO
Minor breaking changes
_refs
and _edits
are now potentially null
The type of _refs
and _edits
is now nullable.
const
Person =
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
import co
co.map({
map<{ name: z.z.ZodString; age: z.z.ZodNumber; }>(shape: { name: z.z.ZodString; age: z.z.ZodNumber; }): CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }> export map
name: z.z.ZodString
name:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
age: z.z.ZodNumber
age:import z
z.number(), }); const
function number(params?: string | z.z.core.$ZodNumberParams): z.z.ZodNumber export number
person =
const person: { name: string; age: number; } & CoMap
Person.
const Person: CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>
create({
create: (init: { name: string; age: number; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
name: string
name: "John",age: number
age: 30 });person.
const person: { name: string; age: number; } & CoMap
CoMap._refs: {}
If property `prop` is a `coField.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; // now nullableperson.
const person: { name: string; age: number; } & CoMap
_edits; // now nullable
CoMap._edits: { name?: LastAndAllCoMapEdits<string> | undefined; age?: LastAndAllCoMapEdits<number> | undefined; }
members
and by
now return basic Account
We have removed the Account schema registration, so now members
and by
methods now always return basic Account
.
This means that you now need to rely on useCoState
on them to load their using your account schema.
function
GroupMembers({
function GroupMembers({ group }: { group: Group; }): React.JSX.Element
group: Group
group }: {group: Group
group:class Group
Group }) { constmembers =
const members: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }[]
group: Group
group.members; return ( <
Group.members: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }[]
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> {members.
const members: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }[]
Array<{ id: string; role: AccountRole; ref: Ref<Account>; account: Account; }>.map<React.JSX.Element>(callbackfn: (value: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }, index: number, array: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }[]) => React.JSX.Element, thisArg?: any): React.JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.map((member) => ( <
member: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }
GroupMemberDetails
function GroupMemberDetails({ accountId }: { accountId: string; }): React.JSX.Element
accountId: string
accountId={member.
member: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }
account: Account
account.Account.id: string
id}React.Attributes.key?: React.Key | null | undefined
key={member.
member: { id: string; role: AccountRole; ref: Ref<Account>; account: Account; }
account: Account
account.Account.id: string
id} /> ))} </JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> ); } functionGroupMemberDetails({
function GroupMemberDetails({ accountId }: { accountId: string; }): React.JSX.Element
accountId: string
accountId }: {accountId: string
accountId: string }) { constaccount =
const account: ({ profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile; root: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<...>; } & { ...; } & CoMap; } & { ...; } & Account) | null | undefined
useCoState(
useCoState<AccountSchema<{ root: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>; profile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<...>; }>; }>, { ...; }>(Schema: AccountSchema<...>, id: string | undefined, options?: { ...; } | undefined): ({ ...; } & ... 1 more ... & Account) | ... 1 more ... | undefined
MyAppAccount,
const MyAppAccount: AccountSchema<{ root: CoMapSchema<{ pets: CoListSchema<CoMapSchema<{ name: z.z.ZodString; age: z.z.ZodNumber; }>>; }>; profile: CoProfileSchema<{ name: z.z.ZodString; age: z.ZodOptional<...>; }>; }>
accountId: string
accountId, {resolve: {
resolve?: RefsToResolve<{ ...; } & Account, 10, []> | undefined
profile: true,
profile?: RefsToResolve<{ name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile, 10, [0]> | undefined
root: {
root?: RefsToResolve<{ pets: CoList<...> | null; } & CoMap, 10, [...]> | undefined
pets: {
pets?: RefsToResolve<CoList<({ name: string; age: number; } & CoMap) | null>, 10, [0, 0]> | undefined
$each: true }, }, }, }); return ( <
$each: RefsToResolve<{ name: string; age: number; } & CoMap, 10, [0, 0, 0]>
JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>{account?.
const account: ({ profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile; root: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<...>; } & { ...; } & CoMap; } & { ...; } & Account) | null | undefined
profile.
Account.profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile
Profile.name: string | undefined
name}</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul>{account?.
const account: ({ profile: { name: string; age: number | undefined; inbox: string | undefined; inboxInvite: string | undefined; } & CoMap & Profile; root: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<...>; } & { ...; } & CoMap; } & { ...; } & Account) | null | undefined
root.
Account.root: { pets: ({ name: string; age: number; } & CoMap)[] & CoList<...>; } & { pets: CoList<...> | null; } & CoMap
pets.
pets: ({ name: string; age: number; } & CoMap)[] & CoList<({ name: string; age: number; } & CoMap) | null>
Array<T>.map<U>(callbackfn: (value: { name: string; age: number; } & CoMap, index: number, array: ({ name: string; age: number; } & CoMap)[]) => U, thisArg?: any): U[] (+1 overload)
Calls a defined callback function on each element of an array, and returns an array that contains the results.map((pet) => <
pet: { name: string; age: number; } & CoMap
JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li>{pet.
pet: { name: string; age: number; } & CoMap
name: string
name}</JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li>)}</JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul> </JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> ); }