Entry pipeline
The order in which qino reads, validates, and shapes an entry, and where to transform data.
Every getter builds its entries through the same steps, in the same order. Each step only sees what the previous ones produced.
Steps
- Parse. qino reads the file. For Markdown, MDX, and
.markdown, it parses the frontmatter with js-yaml. For.json, it parses the whole file. See Frontmatter. - Validate. The schema validates the parsed data. Schema transforms run here. Reserved fields are rejected in both the input and the output. See Schemas.
- Add qino fields. qino adds
markdownandrawto Markdown entries,_statswhen the primitive setsmarkdownStats, and_metato every entry. See MarkdownStats. - Read images. Image fields of
mediaare replaced by aQinoImage, with their dimensions and a blur placeholder. Reading fails when an image is missing, of another type, or over itsmaxSize. See Media. - Resolve relations. Relation references are replaced by their target entries, up to the view's
resolveRelationsdepth. See Relations. - Augment. The view's
augmentadds derived fields. - Filter. For collections, the view's
filterdrops entries.getEntrythrows when its entry is dropped. - Sort. For
getEntries, the view'ssortorders the result.
Steps 5 to 8 come from the selected view. See Views.
| Step | Receives |
|---|---|
| Schema | The parsed data, with keys and values as written in the file |
relations paths | The schema output |
media paths | The schema output |
titleField | The schema output |
augment | The schema output, qino fields, images, and resolved relations |
filter and sort | The augmented entry |
Some reads never run views:
getTree,getFlatTree,getAncestorNodes,getNextNode, andgetPreviousNodestop after validation. They readtitleFieldfrom the schema output.qino checkruns steps 1 to 4 on every file, then checks media fields and body images against theirmediarules.- Resolved relation targets go through steps 1 to 4, then resolve their own relations to the remaining depth. Their own views never run.
Transforming data
qino has no transform option. Reshape data in the schema instead: most Standard Schema validators accept a transform callback, and qino runs it during validation, in step 2. The example below turns a comma-separated tags string into an array.
schema: z
.object({ title: z.string(), tags: z.string() })
.transform((post) => ({
...post,
tags: post.tags.split(",").map((tag) => tag.trim()),
})),
schema: v.pipe(
v.object({ title: v.string(), tags: v.string() }),
v.transform((post) => ({
...post,
tags: post.tags.split(",").map((tag) => tag.trim()),
})),
),
schema: type({ title: "string", tags: "string" }).pipe((post) => ({
...post,
tags: post.tags.split(",").map((tag) => tag.trim()),
})),
The schema validates the data as written, so validation errors name the fields in the file. Everything after it sees the transformed output, including relations and media paths, titleField, views, and the types from Infer.
A transform must:
- be synchronous. A validator that returns a Promise is rejected.
- return an object.
- not add
_meta, normarkdown,raw, or_statsin Markdown entries. qino adds those in step 3.
Transform or augment?
Both derive data. They run at different steps:
| Schema transform | View augment | |
|---|---|---|
| Runs | Step 2, once per file | Step 6, per view |
| Sees | The file's data | Schema output, qino fields, images, resolved relations |
| Can | Rename, remove, and reshape fields | Add fields only |
| Async | No | Yes |
| Applies to | Every view, relations, qino check | The view that declares it |
Use a transform to change the shape of your content. Use augment for values that need the body, a related entry, or async work, such as reading time or an author's name.
Example: camel case keys
qino returns keys as written in the source. To read published_at as publishedAt, rename the keys in a transform, for instance with camelcase-keys:
import camelcaseKeys from "camelcase-keys";
export const postCollection = qino.defineCollection({
directory: "posts",
extension: ".md",
schema: z
.object({ published_at: z.string(), main_author: z.string() })
.transform((post) => camelcaseKeys(post)),
relations: { mainAuthor: authorCollection },
});
Relation and media paths use the camelCased keys, since they point into the schema output. _meta, markdown, and raw are added after the transform and keep their names.