Skip to content

refine

Type: object

Signature: schema.refine(predicate, options?)

What It Is

refine adds a post-parse rule that runs after the base schema has already validated structure and primitive constraints. It receives the parsed value and can enforce cross-field conditions that normal field-level validators cannot express, such as dependencies between optional keys. When the predicate returns false, mdshape emits refine_failed (or a custom code) and can target a specific nested path through options.path.

When to Use

Use refine when validity depends on relationships inside the parsed object instead of isolated constraints on each field. A common example is conditional requirements, like making owner mandatory only when priority is present, while keeping both fields optional in general parsing flows. Avoid using it for simple checks already handled by native validators, because that duplicates logic and reduces schema readability.

schema.refine(predicate, options?)

Input Markdown

md
# RUNBOOK: Object Refine

## 0. META

- service: fraud-api
- priority: 3
- owner: risk-platform

Schema

ts
import { md } from "@markschema/mdshape";

const metaSchema = md
  .section("0. META")
  .fields({
    service: md.string().min(3),
    priority: md.coerce.number().pipeline(md.number().int().min(0)).optional(),
    owner: md.string().min(3).optional(),
  })
  .refine(
    (data) => (data.priority !== undefined ? data.owner !== undefined : true),
    {
      path: ["owner"],
      message: "owner is required when priority is present",
    },
  );

const schema = md.document({
  title: md.heading(1),
  data: metaSchema,
});

Result

Success

json
{
  "success": true,
  "data": {
    "title": "RUNBOOK: Object Refine",
    "data": {
      "service": "fraud-api",
      "priority": 3,
      "owner": "risk-platform"
    }
  }
}

Error

Failure trigger: remove owner while keeping priority; the refine predicate fails and reports the issue at data.owner.

json
{
  "success": false,
  "error": {
    "issues": [
      {
        "code": "missing_heading",
        "message": "Missing heading with depth 1",
        "path": [
          "title"
        ],
        "line": 1,
        "position": {
          "start": {
            "line": 1,
            "column": 1
          }
        }
      }
    ]
  }
}