Skip to main content

Rolling Out New Allowed Types

This guide shows how to safely add a new TreeNodeSchema to an existing AllowedTypes in a SharedTree schema without breaking forwards compatibility with existing applications.

Directly adding a new node type to an existing field would cause old clients to be unable to load documents whose schema contain the new type, breaking them immediately. The following process stages changes to introduce a new allowed type in a non-breaking way.

Step-by-Step Guide

The example below walks through the process of adding string support to a field that previously only supported numbers. The full code can be found in our tests. There are some differences because this guide describes the example as a real scenario involving code deployments while the full code example uses test utilities to emulate this.

We start with a schema that only supports numbers and initialize the tree with it:

// Schema A: only number allowed
const schemaA = SchemaFactoryAlpha.optional([SchemaFactoryAlpha.number]);

// initialize with schema A
const configA = new TreeViewConfiguration({
schema: schemaA,
});
const viewA = treeA.viewWith(configA);
viewA.initialize(5);
synchronizeTrees();

Step 1: Define and Stage the New Allowed Type

To add support for reading strings, we import the alpha APIs and use staged to mark the new allowed type:

// Schema B: number or string (string is staged)
const schemaB = SchemaFactoryAlpha.optional([
SchemaFactoryAlpha.number,
SchemaFactoryAlpha.staged(SchemaFactoryAlpha.string),
]);

Step 2: Upgrade the Schema to Deploy Reading Support

We can now deploy schemaB to clients as a complete replacement to schemaA. At this point, new clients will be able to read documents containing strings and old documents will not. This is safe because no clients have the ability to insert strings into the document.

// view second tree with schema B
const configB = new TreeViewConfiguration({
schema: schemaB,
});
const viewB = treeB.viewWith(configB);
// check that we can read the tree
assert.deepEqual(viewB.root, 5);
// upgrade to schema B: this is a no-op
viewB.upgradeSchema();
synchronizeTrees();

// check view A can read the document
assert.deepEqual(viewA.root, 5);
// check view B cannot write strings to the root
assert.throws(() => {
viewB.root = "test";
});

For this schema change, the schema does not need to be upgraded because staged allowed types are removed from stored schemas. Upgrading the schema would result in a noop.

Step 3: Wait for Client Saturation

Ensure all clients in the deployment have the staged support before proceeding. Monitor the deployment to confirm coverage.

See the staged rollouts section for more details on this step.

Step 4: Upgrade to Allow Inserting the New Allowed Type

Once client saturation has been reached, remove the staged() wrapper from the new allowed type and call upgradeSchema to make a change to the stored schema:

// Schema C: number or string, both fully allowed
const schemaC = SchemaFactoryAlpha.optional([
SchemaFactoryAlpha.number,
SchemaFactoryAlpha.string,
]);

// view third tree with schema C
const configC = new TreeViewConfiguration({
schema: schemaC,
});
const viewC = treeC.viewWith(configC);
// upgrade to schema C and change the root to a string
viewC.upgradeSchema();
viewC.root = "test";
synchronizeTrees();

Any client code changes to add functionality for inserting the new allowed type can also be deployed in this step.

See Also