Skip to main content

Writing a Schema

A schema defines the shape of your documents. You author it in your schema package using the builders from @palantir/pack.schema, and the SDK generator turns it into typed, versioned read and write APIs. If your application was created using the @palantir/pack.create-app CLI, this will be in packages/schema/src/schema.mjs.

Records and unions​

A record is a named set of typed fields, defined with defineRecord. Field types come from the primitives on the imported namespace — String, Double, Boolean, Optional, Array — or references to other records and unions.

A union is a choice between several record variants, defined with defineUnion.

Versions​

A schema is a chain of versions. Version 1 is a single call to defineSchema. Each later version builds on the previous one with nextSchema, composing one or more named defineSchemaUpdate steps.

The module's default export must be the latest version. The generator follows the chain back to the minimum supported version, emitting per-version types and the machinery to upgrade older documents.

Evolving records & unions​

A schema update function receives a builder for each existing record and union, and must return any changed or created primitives. Adding new records and unions is done using defineRecord and defineUnion, as before.

For modifying existing records:

  • .addField(name, type, { derivedFrom }) — add a field. derivedFrom indicates which existing fields can be used to derive a new field on an existing document.
  • .deprecateField(name, message) — mark an existing field as deprecated.
  • .build() — return the updated record.

Note: a field cannot be changed or removed from a record once added, only deprecated. This is to ensure documents written at older versions are always readable by newer clients.

For a modifying existing unions:

  • .addVariant(name, value) - create a new variant of the union, with a value type a nested record or union.
  • .build() — return the updated union.

Note: a variant cannot be changed or removed from a union once added. This is to ensure documents written at older versions are always readable by newer clients.

Example​

import * as S from "@palantir/pack.schema";

// v1: the initial schema.
const ShapeBox = S.defineRecord("ShapeBox", {
docs: "A box.",
fields: {
top: S.Double,
left: S.Double,
color: S.Optional(S.String),
},
});

const schemaV1 = S.defineSchema({ ShapeBox });

// v2: split `color` into separate fill and stroke colors. `derivedFrom`
// back-fills the new fields when reading v1 documents.
const splitColor = S.defineSchemaUpdate(
"splitShapeColorIntoFillAndStroke",
schema => {
const ShapeBox = schema.ShapeBox
.addField("fillColor", S.Optional(S.String), { derivedFrom: ["color"] })
.addField("strokeColor", S.Optional(S.String), { derivedFrom: ["color"] })
.deprecateField("color", "Use fillColor and strokeColor instead.")
.build();
return { ShapeBox };
},
);

const schemaV2 = S.nextSchema(schemaV1).addSchemaUpdate(splitColor).build();

// The default export is the latest version.
export default schemaV2;

API reference​

See the full @palantir/pack.schema API reference for every builder, primitive, and type.