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

@flatmark/core

v0.1.0

Published

A query engine that treats markdown files as a database

Downloads

190

Readme

flatmark

A query engine that treats markdown files as a database.

Drop it into any project. No server. No migrations. Just files.

const db = new FlatMark('./content')
await db.load()

const posts = db.collection('posts')
  .where({ draft: false })
  .orderBy('date', 'desc')
  .limit(10)
  .get()

Why

Markdown files are already how most developers store content — blog posts, docs, notes, changelogs. But querying them means either hand-rolling file reads and frontmatter parsing, or pulling in a full CMS.

Flatmark gives you a query API over a folder of .md files. It parses YAML frontmatter, builds an in-memory index on startup, and lets you filter, sort, paginate, and write — all synchronously after the initial load.

Think SQLite but for markdown.


Install

npm install @flatmark/core

How it works

One folder = one collection. Flatmark reads every .md file in a directory, parses its YAML frontmatter, and stores the result in memory. Queries run against that index — no disk I/O after load.

content/
  posts/        → db.collection('posts')
    hello.md
    world.md
  authors/      → db.collection('authors')
    jane.md

Each file becomes a record:

{
  _id: 'hello-world',           // filename without .md
  _path: '/content/posts/hello-world.md',
  _body: '# Hello\n\nContent…', // markdown body after frontmatter
  title: 'Hello World',         // all frontmatter fields at top level
  date: '2024-01-15',
  draft: false,
  tags: ['typescript', 'markdown']
}

Frontmatter uses YAML — compatible with Obsidian, Hugo, Astro, Jekyll, and most markdown tooling.


Usage

Init

import { FlatMark } from '@flatmark/core'

const db = new FlatMark('./content')
await db.load()

Query

// All published posts, newest first
const posts = db.collection('posts')
  .where({ draft: false })
  .orderBy('date', 'desc')
  .get()

// First post matching a slug
const post = db.collection('posts')
  .where({ slug: 'hello-world' })
  .first()  // → FlatRecord | null

// Count drafts
const drafts = db.collection('posts')
  .where({ draft: true })
  .count()

// Paginate
const page2 = db.collection('posts')
  .where({ draft: false })
  .orderBy('date', 'desc')
  .limit(10)
  .offset(10)
  .get()

// Select specific fields (always includes _id and _path)
const titles = db.collection('posts')
  .where({ draft: false })
  .select(['title', 'date'])
  .get()

Filter operators

// Comparison
.where({ views: { $gt: 100 } })
.where({ views: { $gte: 100, $lt: 1000 } })
.where({ date: { $lte: '2024-12-31' } })

// Equality / inequality
.where({ status: { $ne: 'archived' } })

// Arrays
.where({ tags: { $includes: 'typescript' } })

// Field presence
.where({ hero: { $exists: true } })
.where({ hero: { $exists: false } })

Multiple .where() calls are ANDed together:

.where({ draft: false })
.where({ tags: { $includes: 'typescript' } })
// → draft === false AND tags includes 'typescript'

Write

// Create a new file
await db.collection('posts').insert({
  _id: 'my-new-post',
  title: 'My New Post',
  date: '2024-06-01',
  draft: true,
  _body: '# My New Post\n\nContent here.'
})
// → writes content/posts/my-new-post.md

// Update frontmatter fields (partial — only provided fields change)
await db.collection('posts').update('my-new-post', {
  draft: false
})

// Delete
await db.collection('posts').delete('my-new-post')
// → removes content/posts/my-new-post.md

All write operations are async (disk I/O). Read queries are synchronous (in-memory).


Schema validation

Pass a Zod schema to validate frontmatter on writes. The core library works without Zod — validation is opt-in.

npm install zod
import { z } from 'zod'

const PostSchema = z.object({
  title: z.string(),
  date: z.string(),
  draft: z.boolean().default(false),
  tags: z.array(z.string()).optional()
})

const db = new FlatMark('./content', {
  collections: {
    posts: { schema: PostSchema }
  }
})
await db.load()

// Throws FlatMarkValidationError if frontmatter doesn't match
await db.collection('posts').insert({
  _id: 'bad-post',
  title: 'Missing date'  // ← date is required
})

Live file watching

For dev servers and live-reloading environments:

const db = new FlatMark('./content')
await db.load({ watch: true })

// Index updates automatically when files change on disk

// Clean up when done
await db.close()

Error handling

All errors throw. No result objects.

| Class | When | |---|---| | FlatMarkError | Base class | | FlatMarkNotFoundError | .update() or .delete() on a missing _id | | FlatMarkValidationError | Schema validation failure on write | | FlatMarkCollectionError | .collection() called with a name that has no matching directory |

import { FlatMarkNotFoundError } from '@flatmark/core'

try {
  await db.collection('posts').update('ghost', { draft: false })
} catch (err) {
  if (err instanceof FlatMarkNotFoundError) {
    console.log('Post not found')
  }
}

API reference

new FlatMark(basePath, options?)

| Option | Type | Description | |---|---|---| | basePath | string | Directory to use as the database root | | options.collections | Record<string, CollectionOptions> | Per-collection options (e.g. schema) |

db.load(options?)

Reads all .md files and builds the in-memory index. Must be called before any queries or writes.

| Option | Type | Default | Description | |---|---|---|---| | watch | boolean | false | Start a file watcher for live updates |

db.collection(name)

Returns the Collection for the given directory name. Throws FlatMarkCollectionError if no matching directory was found during load().

db.close()

Stops the file watcher. No-op if watch mode was not enabled.

Collection — read

| Method | Returns | Description | |---|---|---| | .where(filter) | QueryBuilder | Start a query with a filter | | .query() | QueryBuilder | Start a query with no filter (all records) |

QueryBuilder — chain

| Method | Description | |---|---| | .where(filter) | Add another filter (ANDed with previous) | | .select(fields) | Return only these fields (plus _id, _path) | | .orderBy(field, dir?) | Sort by field, 'asc' (default) or 'desc' | | .limit(n) | Return at most n records | | .offset(n) | Skip the first n records |

QueryBuilder — terminal

| Method | Returns | Description | |---|---|---| | .get() | FlatRecord[] | Execute query, return all matches | | .first() | FlatRecord \| null | Execute query, return first match or null | | .count() | number | Count matches (ignores limit/offset) |

Collection — write

| Method | Returns | Description | |---|---|---| | .insert(record) | Promise<FlatRecord> | Create a new .md file. _id required. | | .update(id, fields) | Promise<FlatRecord> | Partial update of frontmatter and/or body | | .delete(id) | Promise<void> | Delete the file and remove from index |


Requirements

  • Node.js 18+
  • TypeScript 5+ (if using types)

License

MIT