Jazz 0.16.0 - Cleaner separation between Zod and CoValue schemas
This release introduces a cleaner separation between Zod and CoValue schemas, improves type inference with circular references, and simplifies how you access internal schemas. While most applications won't require extensive refactors, some breaking changes will require action.
Motivation
Before 0.16.0, CoValue schemas were a thin wrapper around Zod schemas. This made it easy to use Zod methods on CoValue schemas, but it also prevented the type checker from detecting issues when combining Zod and CoValue schemas.
For example, the following code would previously compile without errors, but would have severe limitations:
import { co, z } from "jazz-tools"; const Dog = co.map({ breed: z.string(), }); const Person = co.map({ pets: z.array(Dog), }); // You can create a CoMap with a z.array field that contains another CoMap const map = Person.create({ pets: [Dog.create({ breed: "Labrador" })], }); // But then you cannot eagerly load the nested CoMap, because // there's a plain JS object in between. So this would fail: Person.load(map.id, { resolve: { pets: { $each: true } } });
Schema composition rules are now stricter: Zod schemas can only be composed with other Zod schemas. CoValue schemas can be composed with either Zod or other CoValue schemas. These rules are enforced at the type level, to make it easier to spot errors in schema definitions and avoid possible footguns when mixing Zod and CoValue schemas.
Having a stricter separation between Zod and CoValue schemas also allowed us to improve type inference with circular references. Previously, the type checker would not be able to infer types for even simple circular references, but now it can!
import { co, z } from "jazz-tools"; const Person = co.map({ name: z.string(), get friends(): CoListSchema<typeof Person> { get friends() { return co.list(Person); }, });
There are some scenarios where recursive type inference can still fail due to TypeScript limitations, but these should be rare.
Breaking changes
The Account root id is now discoverable
In prior Jazz releases, the Account root id was stored encrypted and accessible only by the account owner.
This made it impossible to load the account root this way:
const bob = MyAppAccount.load(bobId, { resolve: { root: true }, loadAs: me });
So we changed Account root id to be discoverable by everyone. This doesn't affect the visibility of the account root, which still follows the permissions defined in its group.
For existing accounts, the change is applied the next time the user loads their account.
No action is required on your side, but we preferred to mark this as a breaking change because it minimally affects access to the account root. (e.g., if in your app the root is public, now users can access other users' root by knowing their account ID)
z.optional()
and z.discriminatedUnion()
no longer work with CoValue schemas
You'll now need to use the co.optional()
and co.discriminatedUnion()
equivalents.
This change may require you to update any explicitly typed cyclic references.
import { co, z } from "jazz-tools"; const Person = co.map({ name: z.string(), get bestFriend(): z.ZodOptional<typeof Person> { return z.optional(Person); get bestFriend(): co.Optional<typeof Person> { return co.optional(Person); } });
CoValue schema types are now under the co.
namespace
All CoValue schema types are now accessed via the co.
namespace. If you're using explicit types (especially in recursive schemas), you'll need to update them accordingly.
import { co, z } from "jazz-tools"; const Person = co.map({ name: z.string(), get friends(): CoListSchema<typeof Person> { get friends(): co.List<typeof Person> { return co.list(Person); } });
Unsupported Zod methods have been removed from CoMap schemas
CoMap schemas no longer incorrectly inherit Zod methods like .extend()
and .partial()
. These methods previously appeared to work but could behave unpredictably. They have now been disabled.
We're keeping .optional()
and plan to introduce more Zod-like methods in future releases.
Internal schema access is now simpler
You no longer need to use Zod's .def
to access schema internals. Instead, you can directly use methods like CoMapSchema.shape
, CoListSchema.element
, and CoOptionalSchema.innerType
.
import { co, z } from "jazz-tools"; const Message = co.map({ content: co.richText(), }); const Thread = co.map({ messages: co.list(Message), }); const thread = Thread.create({ messages: Thread.def.shape.messages.create([ messages: Thread.shape.messages.create([ Message.create({ content: co.richText().create("Hi!"), }), Message.create({ content: co.richText().create("What's up?"), }), ]), });
Removed the deprecated withHelpers
method from CoValue schemas
The deprecated withHelpers()
method has been removed from CoValue schemas. You can define helper functions manually to encapsulate CoValue-related logic.
Learn how to define helper methods.