Skip to content

Data modeling

Beyond the primitives, DORM models arrays, embedded documents, and enums — each with real type inference and recursive validation.

ts
const User = dorm.model("users", {
  email:   f.string({ required: true, unique: true }),  // → unique index
  role:    f.enum(["admin", "member"]),                 // typed "admin" | "member"
  tags:    f.array(f.string()),                         // string[]
  address: f.embedded({                                 // typed nested object
    city: f.string({ required: true }),
    zip:  f.string({ default: "00000" }),
  }),
});

Arrays

f.array(element) validates every element against element (which is itself a field builder). Elements are coerced too — an array of f.ref(...) accepts hex strings and stores ObjectIds.

ts
tags: f.array(f.string());
scores: f.array(f.number());
members: f.array(f.ref("users"));

Embedded documents

f.embedded(schema) validates a nested object against its own schema — including required checks, defaults, and coercion. The result type is the inferred object; required sub-fields are mandatory on create, defaulted/optional ones may be omitted.

ts
address: f.embedded({
  city: f.string({ required: true }),
  zip:  f.string({ default: "00000" }),
});

await User.objects.create({ email, address: { city: "NYC" } }); // zip → "00000"

Enums

f.enum(values) constrains a value to a literal union and validates membership.

ts
role: f.enum(["admin", "member", "guest"]); // typed as "admin" | "member" | "guest"

Indexes

Mark fields unique: true or index: true, then create the indexes once after connecting:

ts
const User = dorm.model("users", {
  email: f.string({ required: true, unique: true }),
  name:  f.string({ index: true }),
});

await dorm.connect();
await dorm.ensureIndexes();      // all models
// or a single one:
await User.ensureIndexes();

unique fields are also checked app-side on create and update — a duplicate raises a clean validation error (DORM_ERROR_4) instead of a raw driver error. See Lifecycle & transactions.

Populate

populate(...refs) hydrates ref fields, replacing each id with the referenced document (an outer join, so documents with a missing ref are kept):

ts
const [post] = await Post.objects.filter({ id }).populate("author");
post.author; // the full user document, not just an ObjectId

Released under the MIT License.