Announcing Convex 0.14.0

Meet the brand new Convex Rust client! We’ve also open sourced the JS client, added schema validation, and more. 0.14.0 is the best version of Convex yet!

The main updates in 0.14.0 are:

  • Rust Client
  • Open Sourced JS Client
  • Breaking: Schema Validation
  • Breaking: Actions are now Parallelized
  • Minor Improvements

Full details below.

Rust Client

We’ve just released a Convex Rust client! It’s 100% open source and available at https://github.com/get-convex/convex-rs.

It has full support for the entire Convex platform. This includes subscribing to query functions along with running mutations and actions. We’re excited to see what you all will build with it.

You can get started with the new Rust quickstart guide. The full docs are available at https://docs.rs/convex/latest/convex/.

Open Sourced JS Client

Starting with 0.14.0 The entire convex npm package is open source and available at https://github.com/get-convex/convex-js! This includes:

Breaking: Schema Validation

As of 0.14.0, Convex will validate that all existing and new documents match your database schema.

Previously, schemas were used to create TypeScript types but nothing enforced at runtime that the documents actually matched the schema. It was possible to use an as any cast, JavaScript code, or the dashboard to insert documents that didn’t conform to the schema. That ends today!

After upgrading to 0.14.0, when you push your schema with npx convex dev or npx convex deploy, Convex will:

  1. Validate that all existing documents match the schema. If not, the push will will be rejected.
  2. Validate that all new documents or edits to documents also match the schema. If not, the mutation will fail.

Combined, this enables you to have confidence that the data in Convex actually matches the schema.

In general, there is nothing you need to change in order to upgrade. Keeping your schema as-is should work for most apps.

That being said, if your app already contains documents that don’t match your schema, you’ll be forced to fix them. Additionally, there are some patterns listed below that could take additional work to function with schema validation.

If you don’t want to use schema validation, you can opt out of it with

import { defineSchema } from "convex/schema";

export default defineSchema(
  {
    // Define tables here.
  },
  {
    schemaValidation: false,
  }
);

As always, we’d love to help you upgrade and hear your feedback. Please reach out in our Discord community.

Pro-tip!

Use the dashboard to find documents that don’t match your schema. We’ve added new filter operators to allow you to search for documents that have or don’t have a specific type:

strictstrictTableNameTypes

The options passed to defineSchema have changed slightly. The strict option no longer exists and has been replaced with strictTableNameTypes.

strictTableNameTypes determines whether the TypeScript table names types produced from your schema allow accessing tables not in the schema.

By default, the TypeScript table name types produced by your schema are strict. That means that they will be a union of strings (ex. "messages" | "channels") and only support accessing tables explicitly listed in your schema.

When strictTableNameTypes is disabled, the TypeScript types will allow access to tables not listed in the schema and their document type will be any.

This matches the behavior of the old strict flag, except that strict also adjusted the document types in your schema to allow extra properties. That is no longer an option in 0.14.0. See “Object Schema Validation” below for details.

You can disable strictTableNameTypes by passing in strictTableNameTypes: false:

import { defineSchema } from "convex/schema";

export default defineSchema(
  {
    // Define tables here.
  },
  {
    strictTableNameTypes: false,
  }
);

Regardless of the value of strictTableNameTypes, your schema will only validate documents in the tables listed in the schema. You can still create and modify documents in other tables in JavaScript or on the dashboard (they just won't be validated).

Object Schema Validation

Convex’s schema validation doesn’t permit objects to have properties that aren’t listed in the schema. So if you have a table like:

import { defineSchema, defineTable } from "convex/schema";
import { v } from "convex/values";

export default defineSchema({
  myTable: defineTable({
    stringProperty: v.string(),
  }),
});

then Convex will not allow you to insert documents into it like:

import { mutation } from "./_generated/server";

export default mutation(async ({ db }) => {
  const document = { stringProperty: "abc", numberProperty: 123 };
  await db.insert("myTable", document);
});

This will error because the document contains numberProperty but the schema doesn’t define it. Note that this is different than how TypeScript works. TypeScript allow extra properties in many cases.

To fix this, add numberProperty to the schema and make it optional if only some documents contain it:

import { defineSchema, defineTable } from "convex/schema";
import { v } from "convex/values";

export default defineSchema({
  myTable: defineTable({
    stringProperty: v.string(),
    numberProperty: v.optional(v.number()),
  }),
});

If this is difficult to do for your app, you can instead mark the table / object as any with defineTable(v.any()) / v.any() or opt out of schema validation entirely with schemaValidation: false.

Circular References

Sometimes schemas contain circular ID references like:

import { defineSchema, defineTable } from "convex/schema";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    preferencesId: v.id("preferences"),
  }),
  preferences: defineTable({
    userId: v.id("users"),
  }),
});

In this schema, documents in the users table contain a reference to documents in preferences and vice versa.

Because schema validation enforces your schema on every db.insert, db.update, and db.patch call, creating circular references like this is difficult to do.

The easiest way around this is to initially create one of the documents with a placeholder ID and replace it with the correct ID afterwards:

import { Id } from "./_generated/dataModel";
import { mutation } from "./_generated/server";

const placeholder = new Id("preferences", "AAAAAAAAAAAAAAAAAAAAAA");

export default mutation(async ({ db }) => {
  const userId = await db.insert("users", { preferencesId: placeholder });
  const preferencesId = await db.insert("preferences", { userId })
  await db.patch(userId, { preferencesId });
});

Other solutions include marking one of the properties as optional with v.optional or opting out of schema validation entirely with schemaValidation: false. We plan to make it easier to create circular references in the future.

Breaking: Actions are now Parallelized

Previously all actions and mutations issued from a single ConvexReactClient were run one at a time as part of a single, serial queue. This was useful for apps like a chat app that want to ensure that a single user’s messages are saved in the same order they were sent.

That being said, running actions as part of this queue significantly slowed them down. One slow action would delay all future actions and mutations.

Given that actions are supposed to be the most flexible type of Convex function (and sometimes can be long-running), we decided that forcing them into this single queue was the wrong choice.

Going forward

  • mutations from a single ConvexReactClient will be executed one at a time in a single, ordered queue.
  • actions from a ConvexReactClient will be executed as soon at they are sent to the server (even if other actions and mutations are running).

If your app relies on actions running after other actions or mutations make sure to only trigger the action after the relevant previous function completes.

Minor Improvements

  • Breaking: the BaseConvexClient.setAuth and ConvexReactClient.setAuth methods no longer return a Promise.
  • The type of auth.getUserIdentity is fixed and now includes a new subject property containing the Clerk/Auth0 user ID.
  • The PropertyValidators and ObjectType types are now exported from convex/values. These are useful for writing middleware with argument validators.
  • The environment variable limit has been increased to 100 per deployment.
  • The generated type of usePaginatedQuery is improved to only include queries that expect a paginationOpts argument.
  • atob and btoa have been fixed to correctly support Latin-1 characters.
  • Improved error messages and stack traces.
  • The output of the Convex CLI is more clear.
  • Convex’s Javascript environment now supports npm nodules that depend on wasm imports such as langchain.