Types
Inferred entry types and generated slug types.
Qino gives you two layers of typing: entry shapes inferred from the schema, and slug unions generated by the CLI.
Inferred entries
Getter results are inferred from the schema output, the selected view's relation depth, the awaited augment output, and _meta. No annotations are needed:
const post = await postCollection.getEntry("hello", { view: "detail" });
post.author.name; // resolved at depth 1
post.authorName; // from augment
post._meta.slug; // string
To name those types, use Infer:
import type { Infer } from "@qino/cms";
type PostTypes = Infer<typeof postCollection>;
type Post = PostTypes["output"]; // same as views.default
type DetailPost = PostTypes["views"]["detail"];
output is one entry; use Array<Post> for a list. views has one key per declared view, including default. Without views, output is the baseline entry and views is empty. Infer works on collections, trees, and items, and exists only in TypeScript.
Generated slugs
qino build writes qino/_generated/types.d.ts, which augments @qino/cms:
declare module "@qino/cms" {
export type PostSlug = "hello-world" | "second-post" | (string & {});
interface QinoSlugRegistry {
"/posts": PostSlug;
}
}
QinoSlugRegistry maps a directory to its slug union. Getters that take a slug look up the registry by the primitive's directory, so getEntry, getTree(slug), getNextNode, and getPreviousNode autocomplete known slugs, and getAllSlugs returns the union.
The union ends in (string & {}), so any string is still accepted. The types are assistive, not strict. Before the first qino build, or for a directory with no entries, the slug type is plain string.
Type names are the directory PascalCased with only the last segment singularized: /posts becomes PostSlug, /docs/v1 becomes DocsV1Slug. Collisions get a numeric suffix. Items have no slugs and are skipped.
Relation depth in types
The resolveRelations depth is reflected in the type. At depth 1, post.author is the author entry and post.author.company stays a string if it is itself a relation. At depth 2, it resolves too. Optional and nullable relation fields keep their optionality.
Augment constraints
Augment output cannot overlap existing entry fields or markdown and raw on Markdown entries. Overlaps are type errors, and runtime errors if the types are bypassed.