Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
02e4e9e
first iteration for schema validation on create
Takeno Jan 6, 2026
55f73fe
fixup! first iteration for schema validation on create
Takeno Jan 6, 2026
cf1e719
validation on creation finished + init comap.set
Takeno Jan 12, 2026
dfca8c0
fixup! validation on creation finished + init comap.set
Takeno Jan 12, 2026
3fd01dd
feat: validation on coMap.set and applyDiff
Takeno Jan 13, 2026
f38e1f5
fixup! feat: validation on coMap.set and applyDiff
Takeno Jan 13, 2026
c00d789
update better-auth test with validation
Takeno Jan 19, 2026
559b359
update inspector tests for runtime validation
Takeno Jan 19, 2026
6c6e20b
fix co.richText usage
Takeno Jan 19, 2026
ebe6387
colist creation and mutation validation
Takeno Jan 19, 2026
6e6c3ad
validation on loading
Takeno Jan 19, 2026
4e52fe0
cleanups
Takeno Jan 19, 2026
de93f37
feedback addressed
Takeno Jan 20, 2026
7fa442c
refactor: zodSchema from CoValueSchema
Takeno Jan 20, 2026
c6b6ed7
coFeed runtime validation
Takeno Jan 20, 2026
1d80d8f
typos
Takeno Jan 20, 2026
b00ae53
coFeed.create validation option type
Takeno Jan 21, 2026
4dea1aa
add more zod validation tests
Takeno Jan 21, 2026
58c23cd
chore: formatting
Takeno Jan 21, 2026
f04ff06
global default validation mode
Takeno Jan 21, 2026
83c2363
strict validation for examples
Takeno Jan 21, 2026
f3f01d3
refactoring from feedback
Takeno Jan 22, 2026
9c658cc
push down data validation on coValue creation
Takeno Jan 22, 2026
f985395
fixup! push down data validation on coValue creation
Takeno Jan 22, 2026
14d5628
coList validationm for unshift and splice
Takeno Jan 22, 2026
038fa14
Update packages/jazz-tools/src/tools/coValues/coList.ts
Takeno Jan 26, 2026
0ebfe36
fixup! coList validationm for unshift and splice
Takeno Jan 22, 2026
6e67485
removed getDefaultValidationMode from exports
Takeno Jan 26, 2026
bbb626d
strict equality checks for Account and Group class in schema validation
Takeno Jan 26, 2026
38a5ddd
ensure validation before coList manipulations
Takeno Jan 26, 2026
25f78a0
expectValidationError overload for async calls
Takeno Jan 26, 2026
b41745e
Pass down localValidationmode when instantiateRefEncodedWithInit is c…
Takeno Jan 26, 2026
398d928
coDiscriminatedUnion validation
Takeno Jan 27, 2026
c84ab34
add unsafeSplice and rename unsafe methods
Takeno Jan 27, 2026
e6ef5fd
ensure validationMode is pass down for nested coValues
Takeno Jan 27, 2026
7b28e97
fixup! coDiscriminatedUnion validation
Takeno Jan 27, 2026
3c93021
Merge branch 'main' into feat/runtime-validation-write
Takeno Jan 27, 2026
d8962f6
addressing perf feedbacks
Takeno Feb 10, 2026
90230a3
Merge branch 'main' into feat/runtime-validation-write
Takeno Feb 10, 2026
2e7223a
fixup! Merge branch 'main' into feat/runtime-validation-write
Takeno Feb 10, 2026
9eb7440
fixup! addressing perf feedbacks
Takeno Feb 10, 2026
85a525d
fixup! fixup! addressing perf feedbacks
Takeno Feb 10, 2026
a6120d4
more tests
Takeno Feb 10, 2026
78b0bae
feat: enforce coValueSchema to be available
gdorsi Feb 10, 2026
3bce7de
feat: makeCodecCoField --> makeCodecSchema
gdorsi Feb 10, 2026
12b4f92
feat: remove legacy _schema
gdorsi Feb 11, 2026
0791cf4
feat: initialize built-in schemas for CoMap and Account, update coVal…
gdorsi Feb 11, 2026
fd80743
refactor: replace resolveSchemaField with cached getDescriptorsSchema…
gdorsi Feb 11, 2026
cca0969
refactor: remove deprecated methods and streamline SchemaUnion handli…
gdorsi Feb 11, 2026
ef4fe05
fix: clean up type castings for schema
gdorsi Feb 11, 2026
dcd2fc7
refactor: optimize schemaUnionDiscriminatorFor by consolidating coVal…
gdorsi Feb 11, 2026
d6fa434
refactor: rename imported classes for clarity and consistency in coVa…
gdorsi Feb 11, 2026
8eb0b75
chore: assertCoValueSchema typed
Takeno Feb 11, 2026
5b880c1
fix: clean up schema builtin checks
gdorsi Feb 11, 2026
554f059
refactor: rename coValueClass subclasses as workaround for bundling bug
gdorsi Feb 11, 2026
1317e90
chore: changelog
gdorsi Feb 11, 2026
28358f7
Merge pull request #3455 from garden-co/feat/goodbye-cofield-
gdorsi Feb 11, 2026
734c671
fix: profile default schema using an hydrated schema def
gdorsi Feb 12, 2026
b641d1a
doc warning about schema validation
Takeno Feb 12, 2026
5b15ff3
fix verify schema.def.type in isUnionSchema
Takeno Feb 12, 2026
d692a24
feat: added local validation mode to coList.applyDiff
Takeno Feb 12, 2026
cdcdad1
changeset
Takeno Feb 12, 2026
1d90cb3
chore: refactoring coValueCreateOption (#3457)
Takeno Feb 12, 2026
507f325
Schema validation with superRefine (#3460)
Takeno Feb 13, 2026
bbe9596
Merge branch 'main' into feat/runtime-validation-write
Takeno Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/runtime-validation-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jazz-tools": patch
---

Introduced runtime validation for schema-based CoValues. All mutations now accept a `validation` option of `strict` or `loose`. `setDefaultValidationMode()` can also be used to enable or disable validation across the entire app. Currently, the default validation mode is `warn`: updates and inserts of invalid data will still be allowed, but a console warning will be issued. The usage of `setDefaultValidationMode("strict")` is encouraged, as it will be the default mode in the future.
5 changes: 5 additions & 0 deletions .changeset/smooth-lions-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jazz-tools": patch
---

Removed the legacy `coField` and `Encoders` exports and completed the runtime schema migration to the new schema descriptors. Apps still using the old schema APIs should migrate to the current `co`/zod based schemas.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking release labeled as patch

Medium Severity

The changeset marks jazz-tools as patch while documenting removal of legacy exports (coField, Encoders). Removing public exports is a breaking API change, so this release metadata can publish incompatible behavior under a non-breaking version bump.

Fix in Cursor Fix in Web

4 changes: 3 additions & 1 deletion examples/chat-rn-expo/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co, z } from "jazz-tools";
import { co, z, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

export const Message = co.map({
text: co.plainText(),
Expand Down
4 changes: 3 additions & 1 deletion examples/chat-rn/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co } from "jazz-tools";
import { co, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

export const Message = co.map({
text: co.plainText(),
Expand Down
4 changes: 3 additions & 1 deletion examples/chat/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co } from "jazz-tools";
import { co, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

export const Message = co
.map({
Expand Down
4 changes: 3 additions & 1 deletion examples/form/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co, z } from "jazz-tools";
import { co, z, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

export const BubbleTeaAddOnTypes = [
"Pearl",
Expand Down
14 changes: 13 additions & 1 deletion examples/inspector/tests/lib/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { FileStream, ImageDefinition, co, z } from "jazz-tools";
import {
FileStream,
ImageDefinition,
co,
z,
setDefaultValidationMode,
} from "jazz-tools";
import {
Issue,
Organization,
Expand All @@ -7,6 +13,8 @@ import {
ReactionsList,
} from "./schema";

setDefaultValidationMode("strict");

const projectsData: {
name: string;
description: string;
Expand Down Expand Up @@ -73,7 +81,9 @@ export const createFile = () => {

export const createImage = () => {
return ImageDefinition.create({
original: FileStream.create(),
originalSize: [1920, 1080],
progressive: false,
placeholderDataURL: "data:image/jpeg;base64,...",
});
};
Expand All @@ -82,6 +92,8 @@ export const createOrganization = () => {
return Organization.create({
name: "Garden Computing",
image: ImageDefinition.create({
original: FileStream.create(),
progressive: false,
originalSize: [1920, 1080],
placeholderDataURL: "data:image/jpeg;base64,...",
}),
Expand Down
4 changes: 3 additions & 1 deletion examples/jazz-nextjs/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co, Group, Loaded, z } from "jazz-tools";
import { co, Group, Loaded, z, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

export const TodoProfile = co
.profile({
Expand Down
4 changes: 3 additions & 1 deletion examples/multi-cursors/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { co, z } from "jazz-tools";
import { co, z, setDefaultValidationMode } from "jazz-tools";
import { Camera, Cursor } from "./types";

setDefaultValidationMode("strict");

export const CursorFeed = co.feed(Cursor);
export type CursorFeed = co.loaded<typeof CursorFeed>;

Expand Down
9 changes: 8 additions & 1 deletion examples/music-player/src/1_schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { co, z, setDefaultSchemaPermissions } from "jazz-tools";
import {
co,
z,
setDefaultSchemaPermissions,
setDefaultValidationMode,
} from "jazz-tools";

setDefaultValidationMode("strict");

setDefaultSchemaPermissions({
onInlineCreate: "sameAsContainer",
Expand Down
4 changes: 3 additions & 1 deletion examples/organization/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { co, z } from "jazz-tools";
import { co, z, setDefaultValidationMode } from "jazz-tools";
import { getRandomUsername } from "./util";

setDefaultValidationMode("strict");

export const Project = co
.map({
name: z.string(),
Expand Down
4 changes: 3 additions & 1 deletion examples/passkey-rn/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co } from "jazz-tools";
import { co, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

/**
* Simple note schema to demonstrate Jazz functionality with passkey auth.
Expand Down
4 changes: 3 additions & 1 deletion examples/todo/src/1_schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co, z } from "jazz-tools";
import { co, z, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

/** Walkthrough: Defining the data model with CoJSON
*
Expand Down
4 changes: 3 additions & 1 deletion examples/vector-search/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { co, z } from "jazz-tools";
import { co, z, setDefaultValidationMode } from "jazz-tools";

setDefaultValidationMode("strict");

export const JazzProfile = co.profile();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ Jazz will automatically create the CoValues for you.
```
</CodeGroup>

<Alert variant="warning" className="flex gap-2 items-center my-4">
Starting from Jazz 0.20.10, it is possible to opt in to run-time schema validation on writes. This will ensure that only data which conforms to the defined schema can be inserted into the CoValue. This helps to enforce data integrity and improve the type-safety of your application. The validation strategy is currently warn by default: updates and inserts of invalid data will still be allowed, but will give a console warning. This can be changed using the setDefaultValidationMode(), which accepts `strict` (throw if invalid), `warn` (warn if invalid) and `loose` (allow invalid), as options.
</Alert>

<Alert variant="info" className="flex gap-2 items-center my-4">
To learn more about how permissions work when creating nested CoValues with plain JSON objects,
refer to [Ownership on inline CoValue creation](/docs/permissions-and-sharing/cascading-permissions#ownership-on-inline-covalue-creation).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,15 @@ export class JazzRepository {

if (!uniqueId) {
// Use the same owner of the table.
const node = schema.create(data, {
owner: list.$jazz.owner,
});
const node = schema.create(
{
...data,
_deleted: false,
},
{
owner: list.$jazz.owner,
},
);

list.$jazz.push(node);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@ export class SessionRepository extends JazzRepository {
},
});

const session = this.getSchema("session").create(data, {
unique: data.token,
owner: this.owner,
});
const session = this.getSchema("session").create(
{
...data,
_deleted: false,
},
{
unique: data.token,
owner: this.owner,
},
);

sessions.$jazz.push(session);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ export class UserRepository extends JazzRepository {
throw new Error("Email already exists");
}

const user = await super.create(model, data, uniqueId);
const user = await super.create(
model,
{
sessions: [],
...data,
},
uniqueId,
);

await this.updateEmailIndex(userEmail, user.$jazz.id);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ describe("JazzBetterAuthDatabaseAdapter tests", async () => {

expect(
handleSyncMessageSpy.mock.calls.filter(([msg]) => msg.id === user.id),
).toHaveLength(3);
).toHaveLength(2);
});

it("should return the new entity with date objects", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,47 +348,6 @@ describe("UserRepository", () => {
expect(newEmailUsers[0]?.$jazz.id).toBe(user.$jazz.id);
});

it("should update user email to null and remove from email index", async () => {
const userRepository = new UserRepository(
databaseSchema,
databaseRoot,
worker,
);

const user = await userRepository.create("user", {
email: "test@example.com",
name: "Test User",
});

// Update email to null
const updatedUsers = await userRepository.update(
"user",
[
{
field: "id",
operator: "eq",
value: user.$jazz.id,
connector: "AND",
},
],
{ email: null },
);

expect(updatedUsers.length).toBe(1);
expect(updatedUsers[0]?.email).toBe(null);

// Verify email is no longer findable
const emailUsers = await userRepository.findMany("user", [
{
field: "email",
operator: "eq",
value: "test@example.com",
connector: "AND",
},
]);
expect(emailUsers.length).toBe(0);
});

it("should handle update with no matching users", async () => {
const userRepository = new UserRepository(
databaseSchema,
Expand Down Expand Up @@ -565,20 +524,6 @@ describe("UserRepository", () => {
name: "Test User",
});

// First set email to null
await userRepository.update(
"user",
[
{
field: "id",
operator: "eq",
value: user.$jazz.id,
connector: "AND",
},
],
{ email: null },
);

// Then delete the user
const deletedCount = await userRepository.deleteValue("user", [
{ field: "id", operator: "eq", value: user.$jazz.id, connector: "AND" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CoMap, co, coField, z } from "jazz-tools";
import { co, z } from "jazz-tools";
import { expect, test } from "vitest";
import { createInviteLink } from "../index.js";
import { setupTwoNodes } from "./utils.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ describe("useCoState", () => {

it("should immediately load deeploaded data when available locally", async () => {
const Message = co.map({
content: CoRichText,
content: co.richText(),
});
const Messages = co.list(Message);
const Thread = co.map({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @vitest-environment happy-dom

import { CoMap, Group, ID, co, coField, z } from "jazz-tools";
import { Group, co, z } from "jazz-tools";
import { assertLoaded } from "jazz-tools/testing";
import { describe, expect, it } from "vitest";
import { createInviteLink, useAcceptInvite } from "../index.js";
Expand Down
Loading
Loading