Skip to content

Schema Validation

Schemas enforce data integrity by validating documents on insert and update. Define field types, constraints, and defaults at collection creation time.

Defining a Schema

Pass a schema object when creating a collection.

js
import { StateDB } from '@phpdot/statedb';

const db = new StateDB('app');
const users = db.createCollection('users', {
  schema: {
    name: { type: String, required: true, minLength: 1, maxLength: 100 },
    email: { type: String, required: true, match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
    age: { type: Number, min: 0, max: 150 },
    role: { type: String, enum: ['admin', 'user', 'guest'], default: 'user' },
    active: { type: Boolean, default: true },
    tags: { type: Array, of: String },
    profile: { type: Object },
    createdAt: { type: Date, default: () => new Date() },
  }
});

Schema Rules

RuleTypeDescription
typeSchemaTypeRequired type for the field value
requiredbooleanField must be present on insert (not enforced on update)
defaultany | () => anyDefault value if field is missing on insert
minnumberMinimum value (for numbers)
maxnumberMaximum value (for numbers)
minLengthnumberMinimum string length
maxLengthnumberMaximum string length
enumany[]Value must be one of the listed values
matchRegExpString must match the regular expression
ofSchemaTypeType constraint for array elements

Type Constructors

Use JavaScript's built-in constructors as type values:

ConstructorValidates
Stringtypeof value === 'string'
Numbertypeof value === 'number' and not NaN
Booleantypeof value === 'boolean'
Datevalue instanceof Date
ArrayArray.isArray(value)
ObjectPlain object (not null, not array)
js
const logs = db.createCollection('logs', {
  schema: {
    message: { type: String, required: true },
    level: { type: Number, min: 0, max: 5 },
    timestamp: { type: Date, default: () => new Date() },
    meta: { type: Object },
    tags: { type: Array, of: String },
    archived: { type: Boolean, default: false },
  }
});

Defaults

Default values are applied on insert when the field is undefined. They are not applied during updates.

Static Defaults

js
const col = db.createCollection('items', {
  schema: {
    status: { type: String, default: 'draft' },
    count: { type: Number, default: 0 },
  }
});

col.insertOne({ name: 'Test' });
// { name: 'Test', status: 'draft', count: 0, __inc: 1 }

Function Defaults

Use a function to generate dynamic defaults. The function is called at insert time.

js
const col = db.createCollection('events', {
  schema: {
    id: { type: String, default: () => Math.random().toString(36).slice(2) },
    createdAt: { type: Date, default: () => new Date() },
  }
});

col.insertOne({ title: 'Event 1' });
// { title: 'Event 1', id: 'k7x9m2p', createdAt: Date, __inc: 1 }

Validation on Insert

All schema rules are checked after defaults are applied.

js
const users = db.createCollection('users', {
  schema: {
    name: { type: String, required: true },
    age: { type: Number, min: 0 },
  }
});

// Missing required field
users.insertOne({ age: 25 });
// Error: "Validation error: name is required"

// Wrong type
users.insertOne({ name: 123 });
// Error: "Validation error: name must be of type String"

// Out of range
users.insertOne({ name: 'Omar', age: -5 });
// Error: "Validation error: age must be at least 0"

Validation on Update

Schema validation also runs on updateOne, updateMany, and replaceOne. On updates, required checks are skipped (you do not need to provide all required fields in a partial update).

js
users.insertOne({ name: 'Omar', age: 30 });

// Type violation on update
users.updateOne({ name: 'Omar' }, { $set: { age: 'thirty' } });
// Error: "Validation error: age must be of type Number"

// Range violation on update
users.updateOne({ name: 'Omar' }, { $set: { age: -1 } });
// Error: "Validation error: age must be at least 0"

String Constraints

minLength / maxLength

js
const col = db.createCollection('posts', {
  schema: {
    title: { type: String, minLength: 1, maxLength: 200 },
  }
});

col.insertOne({ title: '' });
// Error: "Validation error: title must be at least 1 characters"

col.insertOne({ title: 'A'.repeat(201) });
// Error: "Validation error: title must be at most 200 characters"

match

js
const col = db.createCollection('accounts', {
  schema: {
    email: { type: String, match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
  }
});

col.insertOne({ email: 'not-an-email' });
// Error: "Validation error: email does not match required pattern"

Enum Constraint

js
const col = db.createCollection('tasks', {
  schema: {
    priority: { type: String, enum: ['low', 'medium', 'high'] },
  }
});

col.insertOne({ priority: 'urgent' });
// Error: "Validation error: priority must be one of: low, medium, high"

Array Element Type (of)

The of rule validates the type of each element in an array.

js
const col = db.createCollection('lists', {
  schema: {
    tags: { type: Array, of: String },
    scores: { type: Array, of: Number },
  }
});

col.insertOne({ tags: ['a', 'b', 123] });
// Error: "Validation error: tags[2] must be of type String"

Capped Collections

A capped collection automatically removes the oldest documents when it reaches the maximum size.

js
const logs = db.createCollection('logs', {
  schema: {
    message: { type: String, required: true },
  },
  capped: true,
  max: 100,
});

// After 100 inserts, the 101st insert removes the oldest document
for (let i = 0; i < 150; i++) {
  logs.insertOne({ message: `Log ${i}` });
}

logs.count(); // 100 (oldest 50 were evicted)

Error Format

Validation errors are thrown as standard Error objects. When multiple rules fail, all messages are joined with semicolons.

js
try {
  users.insertOne({ age: 'not a number' });
} catch (e) {
  console.log(e.message);
  // "Validation error: name is required; age must be of type Number"
}

The error message format is:

Validation error: {field} {message}[; {field} {message}]...

MIT Licensed