ActiveMDX

With ActiveMDX, you can extract meaningful information from the content of your writing. Structured writing can become structured data. Combine this with the power of React and MDX, and ActiveMDX can be used to build applications that are powered by your writing.

MDX gives us the ability to display markdown writing with React components.

Traditional markdown renders static HTML. MDX renders as React components and gives you the ability to control how each standard markdown html element gets rendered. By itself, this is a very powerful way to display your writing and gives you a lot of creative power. People have used it to build interactive code demos which you can live edit and see the results rendered in real time, and much more.

ActiveMDX takes things a step further by giving you the ability to tap into the AST of your markdown writing and extract elements of your writing as data. This gives you the ability to present your writing in different ways, as well as the ability to automate different tasks using your writing by converting it into JSON objects.

Collections of structured documents

ActiveMDX provides a Collection class that turns a folder of MDX documents into a queryable database. Each different type of document is a Model, or a table in a database. Each document of those types are the rows in that database.

import { Collection } from "@active-mdx/core"

import Epic from "./models/Epic"
import Story from "./models/Story"

const collection = new Collection({
  rootPath: "./docs"
})

await collection.load()

const completedStories = await Story.query((qb) =>
  qb.where("status", "completed")
).fetchAll()

// register a common query
Epic.query("completedStories", (qb) => qb.where("status", "completed"))

// run the common query
Epic.query("completedStories")

Models to represent a structured document

An ActiveMDX Model takes a document which conforms to a common structure, and turns it into a JavaScript object that can be used in any way you can imagine.

import { Model } from "@active-mdx/core"

export default class Story extends Model {
  // This lets you validate / lint your documents to make sure they
  // have the data you expect
  static get schema() {
    const { joi } = this

    return joi.object({
      meta: joi.object({
        status: joi
          .string()
          .required()
          .allow("created", "todo", "in-progress", "qa", "completed")
      })
    })
  }

  // You can relate to other model instances based on what is in this document's content
  epic() {
    return this.belongsTo("Epic")
  }

  // When you create new documents, we can pre-fill the metadata
  get defaults() {
    return {
      meta: {
        status: "created",
        estimates: {
          low: 0,
          high: 0
        }
      }
    }
  }

  get isComplete() {
    return this.meta.status === "complete"
  }

  // Get all of the links from the Mockups section of the document
  get mockupLinks() {
    const { toString } = this.document.utils
    return Object.fromEntries(
      this.document
        .querySection("Mockups")
        .selectAll("link")
        .map((link) => [toString(link), link.url])
    )
  }

  // Get a list of acceptance criteria items from the Acceptance Criteria section of the document
  get acceptanceCriteria() {
    const { toString } = this.document.utils
    return this.document
      .querySection("Acceptance Criteria")
      .selectAll("listItem")
      .map(toString)
  }
}

ActiveMDX provides an action system

With the action system, you can run functions using your collection and different models. In the example I've written about here, you could do things like publish all the stories to Github or Jira using their API. To do that we just define the action publish-to-github

Story.action("publish-to-github", async function (story) {
  await githubAPI.issues.create({
    title,
    body: story.document.content,
    labels: story.meta.labels
  })
})

We can then use the AMDX CLI to run that action

$ amdx action publish-to-github stories/authentication/a-user-should-be-able-to-register

The Rest is up to you

ActiveMDX

With ActiveMDX, you can extract meaningful information from the content of your writing. Structured writing can become structured data. Combine this with the power of React and MDX, and ActiveMDX can be used to build applications that are powered by your writing.

MDX gives us the ability to display markdown writing with React components.

Traditional markdown renders static HTML. MDX renders as React components and gives you the ability to control how each standard markdown html element gets rendered. By itself, this is a very powerful way to display your writing and gives you a lot of creative power. People have used it to build interactive code demos which you can live edit and see the results rendered in real time, and much more.

ActiveMDX takes things a step further by giving you the ability to tap into the AST of your markdown writing and extract elements of your writing as data. This gives you the ability to present your writing in different ways, as well as the ability to automate different tasks using your writing by converting it into JSON objects.

Collections of structured documents

ActiveMDX provides a Collection class that turns a folder of MDX documents into a queryable database. Each different type of document is a Model, or a table in a database. Each document of those types are the rows in that database.

import { Collection } from "@active-mdx/core"

import Epic from "./models/Epic"
import Story from "./models/Story"

const collection = new Collection({
  rootPath: "./docs"
})

await collection.load()

const completedStories = await Story.query((qb) =>
  qb.where("status", "completed")
).fetchAll()

// register a common query
Epic.query("completedStories", (qb) => qb.where("status", "completed"))

// run the common query
Epic.query("completedStories")

Models to represent a structured document

An ActiveMDX Model takes a document which conforms to a common structure, and turns it into a JavaScript object that can be used in any way you can imagine.

import { Model } from "@active-mdx/core"

export default class Story extends Model {
  // This lets you validate / lint your documents to make sure they
  // have the data you expect
  static get schema() {
    const { joi } = this

    return joi.object({
      meta: joi.object({
        status: joi
          .string()
          .required()
          .allow("created", "todo", "in-progress", "qa", "completed")
      })
    })
  }

  // You can relate to other model instances based on what is in this document's content
  epic() {
    return this.belongsTo("Epic")
  }

  // When you create new documents, we can pre-fill the metadata
  get defaults() {
    return {
      meta: {
        status: "created",
        estimates: {
          low: 0,
          high: 0
        }
      }
    }
  }

  get isComplete() {
    return this.meta.status === "complete"
  }

  // Get all of the links from the Mockups section of the document
  get mockupLinks() {
    const { toString } = this.document.utils
    return Object.fromEntries(
      this.document
        .querySection("Mockups")
        .selectAll("link")
        .map((link) => [toString(link), link.url])
    )
  }

  // Get a list of acceptance criteria items from the Acceptance Criteria section of the document
  get acceptanceCriteria() {
    const { toString } = this.document.utils
    return this.document
      .querySection("Acceptance Criteria")
      .selectAll("listItem")
      .map(toString)
  }
}

ActiveMDX provides an action system

With the action system, you can run functions using your collection and different models. In the example I've written about here, you could do things like publish all the stories to Github or Jira using their API. To do that we just define the action publish-to-github

Story.action("publish-to-github", async function (story) {
  await githubAPI.issues.create({
    title,
    body: story.document.content,
    labels: story.meta.labels
  })
})

We can then use the AMDX CLI to run that action

$ amdx action publish-to-github stories/authentication/a-user-should-be-able-to-register

The Rest is up to you