Jazz 0.18.0 - New $jazz field in CoValues

This release introduces a new $jazz field to CoValues. This field serves as a namespace for Jazz methods and internal properties.

Motivation

Jazz aims to make CoValues as similar as possible to JSON objects, but collaborative objects have unique requirements that create challenges:

  1. Property conflicts: Developers need to define collaborative objects with custom fields and Jazz-specific properties (id, owner), without naming conflicts
  2. Direct property assignments: Direct assignments clash with modern front-end frameworks that discourage mutations, like Vue, Svelte and the upcoming React Compiler.
  3. Tracking changes to CoValues: Proxies are currently used for tracking changes to CoValues, but they introduce several usability problems. They make debugging and serialization unnecessarily complex, introduce issues with object identity, and behave inconsistently in different environments.

The new $jazz namespace solves these issues by making CoValues behave like readonly JSON objects while providing Jazz-specific methods in a dedicated namespace. This approach enables more ergonomic APIs (like $jazz.set accepting plain JSON) and sets us up to remove proxies entirely in the future.

Changes

The new $jazz field

The new $jazz field is a namespace for Jazz methods and internal properties. It is available on all CoValues, and it provides access to Jazz internal properties and utility methods:

  • id: The ID of the CoValue
  • owner: The owner of the CoValue
  • refs: A map of references to other CoValues
  • getEdits: Returns a list of edits made to the CoValue
  • ensureLoaded & subscribe: Perform sync operations

Streamlined CoValue updates

The $jazz.set method allows you to update CoValues using either CoValues or plain JSON objects. This makes it easier to work with nested data.

const Dog = co.map({
  name: co.plainText(),
});
const Person = co.map({
  name: co.plainText(),
  dog: Dog,
})

const person = Person.create({
  name: "John",
  dog: { name: "Rex" },
});

// Instead of
person.dog = Dog.create({ name: co.plainText().create("Fido") });

// You can now use plain JSON objects
person.$jazz.set("dog", { name: "Fido" });

CoValues are created automatically, and permissions are handled in the same way than when creating CoValues from JSON (see docs).

This feature is now available on all methods that mutate CoValues (like CoList.$jazz.push, CoList.$jazz.splice and CoFeed.$jazz.push).

New CoList Utility Methods

Jazz 0.18.0 introduces two new utility methods for easier CoList editing:

// Remove items
list.$jazz.remove(1); // By index
list.$jazz.remove(item => item.completed); // By predicate

// Keep only items matching the predicate
list.$jazz.retain(item => !item.completed);

They allow editing a CoList in-place with a simpler API than $jazz.splice.

Breaking Changes

Jazz Properties Migration

All Jazz properties are now available in the $jazz namespace.

// Before
coValue.id; 
coValue._owner;
coValue._refs.someProperty;
coValue._edits.someProperty;

// After
coValue.$jazz.id; 
coValue.$jazz.owner;
coValue.$jazz.refs.someProperty;
coValue.$jazz.getEdits().someProperty;

The only exception is the _type field, which has been renamed to $type$ and is still available on CoValues. This allows Typescript to perform type narrowing on CoValues.

CoMap Changes

All CoMap methods have been moved into $jazz, to allow defining any arbitrary key in the CoMap (except for $jazz and $type$) without conflicts.

// Direct assignment
person.name = "Alice"; 
person.$jazz.set("name", "Alice"); 

// Deleting properties
delete person.age; 
person.$jazz.delete("age"); 

// Update multiple properties
person.applyDiff({ 
  name: "Alice",
  age: 42,
});
person.$jazz.applyDiff({ 
  name: "Alice",
  age: 42,
});

For CoMaps created with co.map(), fields are now readonly and direct assignment will cause type errors. CoMaps created with class schemas will throw runtime errors prompting you to use $jazz.set.

CoList Changes

CoLists are now readonly arrays. All mutation methods have been moved to $jazz and using array mutation methods directly will cause type errors. This means CoLists are no longer a subtype of Array. If you were previously treating CoLists as Arrays, you'll need to migrate to using ReadonlyArray.

// Before
list.push("item"); 
list.unshift("first");
list.splice(1, 1, "replacement");
list[0] = "new value";

// After
list.$jazz.push("item"); 
list.$jazz.unshift("first");
list.$jazz.splice(1, 1, "replacement");
list.$jazz.set(0, "new value");

The following methods have been deprecated and should not be used:

  • sort()
  • reverse()
  • fill()
  • copyWithin()

These methods could behave inconsistently with CoLists. $jazz replacements may be introduced in future releases.

Ownership Changes

The $jazz.owner field now always returns a Group (instead of a Group or Account). Having Account owners is discouraged, because it restricts the CoValue to a single user, who cannot be changed. As a best practice, it's a good idea to create a Group for every new CoValue, even if the Group only has a single user in it (this is the default behavior when creating a CoValue with no explicit owner).

CoValues owned by Accounts will now return a Group that represents that Account. It will continue to behave the same way it previously did: trying to add a new member to the Group will fail.

Accounts as CoValue owners will be phased out in a future release.

Removed Features

castAs() Method

The castAs() method has been completely removed as it was an unsafe operation that bypassed type checking and enabled using CoValues in unsupported ways.

toJSON() Changes

The id and _type fields have been removed from toJSON() output for Account, CoMap, CoFeed, and FileStream classes. This aligns with the idea of viewing CoValues as JSON objects.

Group Property Removal

The root and profile fields have been removed from the Group class. These fields were not documented and were not intended to be used by users.

Codemod

We provide a codemod to make the upgrade to the new $jazz field quicker.

The codemod is type-aware, and it is required to have jazz-tools already upgraded to 0.18.

npx jazz-tools-codemod-0-18@latest

Or if you want to run it on a specific path or file:

npx jazz-tools-codemod-0-18@latest ./path/to/your/src

The goal of this codemod is to get you through the 90% of the work quickly, running a TypeScript check after the codemod will help you to find the remaining issues.

Expect situations where code is migrated the wrong way or legacy code that isn't detected by the codemod.