Primate LogoPrimate

Pema and Stores

A Primate Store schema needs both validation behavior and a persistent datatype. Pema calls a schema with that contract Storable.

import db from "@/config/db";
import p from "pema";
import store from "primate/store";

export default store({
  table: "article",
  db,
  schema: {
    id: store.key.primary(p.uuid),
    title: p.string.max(200),
    published: p.boolean.default(false),
    tags: p.json(p.array(p.string)),
  },
});

Pema parses and validates each value before a Store write. The database adapter uses each field's datatype to map it to its own storage representation.

Compatibility

Schema family Direct Store field? Datatype
p.string Yes string
p.boolean Yes boolean
Number and integer families Yes f32, f64, signed/unsigned widths
p.date Yes datetime
p.url Yes url
p.uuid, .v4(), .v7() Yes UUID datatype for that form
p.blob, p.file Yes blob
p.enum(...) Yes u8
p.json(...) Yes json
optional() / default() If the inner schema is Storable Inner datatype
Object, partial, omit and async schemas No Use p.json(inner) if appropriate
Array, tuple, record and dictionary No Use p.json(inner) if appropriate
Union, literal, predicate and constructor No Validation-only
Pure, unknown, null, undefined, function, symbol No Validation-only
derive() result No Validation-only

Do not infer Store suitability merely because a schema has parse() or a useful TypeScript type. OptionalType and DefaultType forward the inner datatype; a wrapper around a validation-only schema is still unsuitable and can fail when a Store asks for its datatype.

Structured JSON

Use p.json(inner) to validate structured JSON and persist it as one field:

const Metadata = p.json(p({
  labels: p.array(p.string),
  featured: p.boolean.default(false),
}));

The parsed output must be JSON-compatible. p.json does not turn Date, URL, bigint or arbitrary class instances into JSON values.

Identifiers

Primate Store keys support unsigned integers, UUIDs and manually supplied strings:

id: store.key.primary(p.u32)
id: store.key.primary(p.uuid)
id: store.key.primary(p.uuid.v4())
id: store.key.primary(p.string, { generate: false })

Generic p.uuid and explicit p.uuid.v7() generate UUIDv7 keys; explicit p.uuid.v4() generates UUIDv4. See Primary keys for generation and foreign key rules.

Integer sequences

Use .sequence() when a Store field needs a persistent, increasing unsigned integer:

const Event = store({
  table: "event",
  db,
  schema: {
    id: store.key.primary(p.uuid),
    number: p.u32.sequence(),
  },
});

Sequences are available on p.u8, p.u16, p.u32, p.u64 and p.u128. The field is optional when inserting but required on the returned and stored record. Ordinary Pema parsing still requires a value: .sequence() is Store metadata, not a context-free default.

Generated values begin at 1. The Store keeps a persistent high-water mark, allocates batches in input order and coordinates concurrent writers so generated values do not collide. Deleting a row never makes its value available again. Allocation may leave gaps, including after an insert fails, so do not interpret gaps as missing records. A sequence is unsuitable for legally gapless invoice, receipt or similar numbering.

An explicit value remains valid input. A value above the high-water mark moves the sequence forward; a lower value does not move it backward. Reaching the unsigned type's upper bound rejects the next generated value instead of wrapping. u64 and u128 sequence values remain exact bigint values.

Store-facing types

Pema exports lower-level types used by Store implementations and generic code:

Type Purpose
Storable Parsed schema with a persistent datatype
DataKey Union of supported datatype names
DataType Datatype-to-JavaScript-value map
StoreSchema Property map whose values are Storable
StoreId<S> Parsed non-nullable id type
InferStore<S> Parsed Store shape with undefined fields optional
InferStoreOut<S> Store output with required parsed id
Id String identifier helper

pema/StoreType is the public Store-schema class entrypoint used by Primate. It parses complete records and exposes partial() for partial record parsing; applications normally receive Store types through primate/store rather than constructing StoreType directly. Its module also exports the Serialized type.

This page covers schema compatibility. Continue to Stores for primary and foreign keys, insert/update operations, atomic batches, relations, lifecycle, migrations and raw database access.

Previous
Errors