npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@airdraft/core

v0.1.23

Published

Airdraft core engine — schema, collections, slug/publish semantics

Readme

@airdraft/core

Runtime engine for Airdraft — the git-native headless CMS. Provides the storage adapters, collection engine, field validation, plugin system, and all shared TypeScript types used across the monorepo.

Installation

npm install @airdraft/core

Exports

Engine

The CMS engine is instantiated internally by @airdraft/next. You interact with it via the server client returned by createCmsClient(). The class is exported for adapter authors:

| Export | Description | |---|---| | CmsEngine | Core engine class. Methods: listEntries, getEntry, createEntry, updateEntry, deleteEntry, getSchema, getCollection. |

Configuration

| Export | Description | |---|---| | defineConfig(config) | Type-safe helper to define a CmsConfig. Pass your adapter, collections (or schemaPath), plugins, basePath, and defaultLocale. | | asCollectionConfig(raw) | Safely casts a JSON schema import to CollectionConfig. Use when importing airdraft.schema.json at runtime. |

Field Validation

| Export | Description | |---|---| | validateFields(data, fields) | Validates a record against a FieldMap. Returns ValidationError[]. | | validateField(name, value, config) | Validates a single field value against its FieldConfig. |

Supported field types

string · text · number · boolean · date · datetime · rich-text · media · url · list · select · multiselect · relation · relations · object · blocks · image (deprecated — use media)

  • rich-text stores Markdown/MDX body content and contributes to wordCount.
  • text stores multi-line plain text (textarea).
  • media supports multiple: true for multi-file fields and accept for MIME filtering.
  • url validates absolute URLs (https://…) and root-relative paths (/…).
  • blocks embeds a sub-schema as a structured object or repeatable array. Sub-fields are defined via fields: Record<string, FieldConfig>.
  • datetime stores an ISO-8601 datetime string.

Storage Adapters

| Export | Description | |---|---| | LocalAdapter | Reads/writes MDX/MD/JSON/YAML files on the local filesystem. Used in development. | | GitHubAdapter | Reads/writes via the GitHub Contents API. Used in production/serverless. |

Errors

| Export | Description | |---|---| | ValidationError | Thrown when field validation fails (HTTP 422). Carries details: Array<{ field, message }>. | | EntryNotFoundError | Thrown when a specific entry does not exist. | | CollectionNotFoundError | Thrown when a collection name is not registered. | | SlugConflictError | Thrown when creating an entry whose slug already exists. | | ConflictError | Thrown on SHA mismatch (optimistic concurrency). | | UnauthorizedError | Thrown when the request lacks valid credentials (HTTP 401). | | ForbiddenError | Thrown when the actor lacks sufficient role permissions (HTTP 403). | | GitHubError | Thrown when the GitHub API returns an unexpected error. |

Types

All shared TypeScript types are exported from this package:

CmsConfig · CollectionConfig · CollectionMap · CmsSchema · FieldType · FieldConfig · StorageAdapter · Plugin · Entry · RichEntry · EntrySibling · RichListResult · FileResult · WriteOptions · DeleteOptions · FileListItem · AuditEvent · MediaItem · InferCollectionData<C>

Use InferCollectionData<C> with asCollectionConfig() to get fully typed entry data from a collection config:

import { asCollectionConfig, InferCollectionData } from '@airdraft/core'
import schema from './airdraft.schema.json'

const posts = asCollectionConfig(schema.collections.posts)
type PostData = InferCollectionData<typeof posts>

AuditEvent.error includes an optional details array (Array<{ field: string; message: string }>) that carries per-field validation failures.

Rich entry types

| Type | Description | |---|---| | RichEntry<TData> | Extends Entry with wordCount, readTime, and prev/next sibling navigation. Returned by all read paths. | | EntrySibling<TData> | Lightweight stub returned as prev/next on RichEntry. Contains slug, a subset of data fields, wordCount, and readTime. | | RichListResult<TData> | Returned by listEntries. Includes entries, total, page, pages, hasNext, hasPrev. | | ListEntriesResult | Deprecated. Alias for RichListResult. |

CollectionConfig reference

interface CollectionConfig {
  path: string                // glob pattern, e.g. 'content/posts/**'
  label?: string              // human-readable name shown in the UI
  titleField?: string         // field used as the display title (defaults to 'title')
  fields: Record<string, FieldConfig>
  format: 'mdx' | 'md' | 'json' | 'yaml'
  defaultSort?: SortField | SortField[]
  slugSource?: string         // field to derive slug from on creation
  previewUrl?: string         // preview URL template, e.g. '/blog/{slug}'
  publish?: boolean           // enable draft/published state
  wordCountFields?: string[]  // override which fields contribute to wordCount
  siblingFields?: string[]    // fields included in prev/next sibling stubs
  calculateReadTime?: (wc: number) => string
  storeComputedFields?: boolean
}

| Option | Type | Default | Description | |---|---|---|---| | titleField | string | 'title' | Field used as the display name in the editor's entry list. | | format | 'mdx' \| 'md' \| 'json' \| 'yaml' | required | File format for entries. Both md and mdx are parsed as Markdown; mdx additionally allows JSX. | | wordCountFields | string[] | all rich-text fields (+ body for MDX/MD) | Fields whose text content is summed for wordCount. | | siblingFields | string[] | all non-rich-text, non-text fields | Fields included in sibling stubs. | | calculateReadTime | (wc: number) => string | 200 wpm | Custom read-time estimator. | | storeComputedFields | boolean | false | When true, writes _computed: { wordCount, readTime } to frontmatter on create/update. |

ListOptions additions

| Option | Type | Description | |---|---|---| | page | number | 1-based page number. Converted to offset = (page - 1) * limit. Takes precedence over offset when both are provided. |

GetOptions additions

| Option | Type | Description | |---|---|---| | siblings | boolean \| SiblingsOptions | When true, resolves prev/next adjacent entries in the collection. Pass an object to override sort, status, filter, or fields. |

SiblingsOptions shape:

{
  sort?:   SortField | SortField[]
  status?: 'published' | 'draft' | 'all'
  filter?: Record<string, unknown | FilterOperator>
  fields?: string[]  // per-call override of CollectionConfig.siblingFields
}

Content utilities

| Export | Description | |---|---| | countWords(markdown) | Counts words in a Markdown/MDX string (strips code blocks, images, links, and headings before counting). | | defaultReadTime(wordCount) | Returns a read-time string at 200 wpm, e.g. "3 min read". |

Plugin API

import { defineConfig } from '@airdraft/core'
import type { Plugin, CmsSchema } from '@airdraft/core'

const myPlugin: Plugin = {
  name: 'my-plugin',

  // Mutate the effective schema (add/remove/modify fields or collections)
  schema(base: CmsSchema): CmsSchema {
    return base // or return a mutated copy
  },

  middleware(req) {
    // Runs before routing — throw UnauthorizedError / ForbiddenError to block
  },

  transformResponse(req, res) {
    // Called after every successful response; may mutate headers
    return res
  },

  hooks: {
    transformEntry(entry, collection) {
      // Enrich entries — inject resolved URLs, computed fields, etc.
      return entry
    },
    onAuditEvent(event) {
      // Receives a wide event for every completed CMS request.
      // event.error?.details contains per-field validation errors.
      console.log(event)
    },
  },
}

Plugin interface reference:

| Member | Type | Description | |---|---|---| | name | string | Unique plugin identifier | | schema? | (schema: CmsSchema) => CmsSchema | Mutate the effective schema — add fields, inject shared types | | typeContributions? | Record<string, CollectionConfig> | Register reusable sub-schemas by name for blocks fields | | middleware? | (req) => void \| Promise<void> | Pre-routing auth / rate-limit enforcement | | transformResponse? | (req, res) => res | Post-response header mutation (e.g. Set-Cookie) | | hooks.transformEntry? | (entry, collection) => entry | Enrich entries on every read | | hooks.onAuditEvent? | (event: AuditEvent) => void \| Promise<void> | Wide-event audit logging |

Changelog

See CHANGELOG.md.