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

@griffin-app/griffin-core

v0.3.5

Published

TypeScript DSL for defining griffin API tests

Downloads

127

Readme

Griffin Core

@griffin-app/griffin-core is the TypeScript DSL for defining Griffin API tests. Tests are written in TypeScript and produce JSON test monitors that can be executed by the monitor executor.

Features

  • TypeScript DSL for defining API checks
  • Chainable API for building test monitors
  • Two builder styles: sequential (linear) and graph (branching/parallel)
  • Support for HTTP requests, waits, assertions, and edges
  • Request configs can reference previous responses – use a callback for .request() and pass state["node"].body["path"] or state["node"].headers["name"] into path, base, headers, or body
  • String templates – use the template tagged template literal to build path or base from variables, secrets, and node refs in one expression
  • Outputs JSON test monitors for execution
  • Optional notifications, locations, secrets, and variables

Installation

npm install @griffin-app/griffin-core

Build from source:

npm install
npm run build

Usage

Create test files in __griffin__ directories. They export JSON test monitors.

Griffin Core provides two builder APIs:

  • createMonitorBuilder: Sequential tests (recommended for most use cases). Steps are connected in order; no manual edges.
  • createGraphBuilder: Full control over the test graph for parallel execution and branching.

Sequential Builder (Recommended for Simple Tests)

The sequential builder connects steps in order. You pass name and frequency (and optional locations, notifications) into createMonitorBuilder. Use .request() for HTTP calls, .wait() for delays, and .assert() for assertions. For .request() you can pass either a static config object or a callback (state) => config to use data from previous node responses (see Referencing previous responses in requests).

import {
  GET,
  createMonitorBuilder,
  Json,
  Frequency,
  Assert,
  variable,
} from "@griffin-app/griffin-core";

const monitor = createMonitorBuilder({
  name: "health-check",
  frequency: Frequency.every(1).minute(),
})
  .request("health", {
    method: GET,
    response_format: Json,
    path: "/health",
    base: variable("api-service"), // or base: "https://api.example.com"
  })
  .assert((state) => [
    Assert(state["health"].status).equals(200),
    Assert(state["health"].body["status"]).equals("ok"),
  ])
  .build();

export default monitor;

Sequential Example with Waits and Assertions

import {
  GET,
  POST,
  createMonitorBuilder,
  Json,
  Frequency,
  WaitDuration,
  Assert,
  variable,
} from "@griffin-app/griffin-core";

const monitor = createMonitorBuilder({
  name: "create-and-verify-user",
  frequency: Frequency.every(5).minute(),
})
  .request("create_user", {
    method: POST,
    response_format: Json,
    path: "/api/v1/users",
    base: variable("api-service"),
    body: { name: "Test User", email: "[email protected]" },
  })
  .assert((state) => [
    Assert(state["create_user"].status).equals(201),
    Assert(state["create_user"].body["data"]["id"]).not.isNull(),
  ])
  .wait("pause", WaitDuration.seconds(2))
  .request("get_user", {
    method: GET,
    response_format: Json,
    path: "/api/v1/users/[email protected]",
    base: variable("api-service"),
  })
  .assert((state) => [
    Assert(state["get_user"].status).equals(200),
    Assert(state["get_user"].body["data"]["name"]).equals("Test User"),
    Assert(state["get_user"].latency).lessThan(500),
  ])
  .build();

export default monitor;

Referencing previous responses in requests

You can use the body or headers of a previous node’s response when building a later request. Pass a callback to .request() instead of a config object. The callback receives the same state proxy as .assert(); use state["nodeName"].body["path"] or state["nodeName"].headers["headerName"] in path, base, headers, or body. Only body and headers can be referenced (not status or latency).

Example: create an order, then confirm it using the order ID from the first response.

import {
  POST,
  createMonitorBuilder,
  Json,
  Frequency,
  Assert,
  variable,
} from "@griffin-app/griffin-core";

const monitor = createMonitorBuilder({
  name: "order-workflow",
  frequency: Frequency.every(30).minute(),
})
  .request("create_order", {
    method: POST,
    base: variable("api-service"),
    path: "/api/v1/orders",
    response_format: Json,
    body: { items: [{ product_id: "ABC", quantity: 2 }] },
  })
  .assert((state) => [
    Assert(state["create_order"].status).equals(201),
    Assert(state["create_order"].body["order_id"]).isDefined(),
  ])
  .request("confirm_order", (state) => ({
    method: POST,
    base: variable("api-service"),
    path: "/api/v1/orders/confirm",
    response_format: Json,
    body: {
      order_id: state["create_order"].body["order_id"],
    },
  }))
  .assert((state) => [Assert(state["confirm_order"].status).equals(200)])
  .build();

export default monitor;

The callback form of .request() uses the same state proxy as .assert(). References like state["create_order"].body["order_id"] are serialized as $nodeRef in the monitor and resolved at execution time by the executor.

Graph Builder (For Complex Workflows)

The graph builder uses addNode and addEdge. Nodes are created with HttpRequest(config), Wait(duration), and Assertion(assertions). Frequency is set in the builder config.

import {
  GET,
  POST,
  createGraphBuilder,
  HttpRequest,
  Wait,
  Json,
  START,
  END,
  Frequency,
  WaitDuration,
  variable,
} from "@griffin-app/griffin-core";

const monitor = createGraphBuilder({
  name: "foo-bar-check",
  frequency: Frequency.every(1).minute(),
})
  .addNode(
    "create_foo",
    HttpRequest({
      method: POST,
      response_format: Json,
      path: "/api/v1/foo",
      base: variable("api-service"),
      body: { name: "test", value: 42 },
    }),
  )
  .addNode("wait_between", Wait(WaitDuration.seconds(2)))
  .addNode(
    "get_foo",
    HttpRequest({
      method: GET,
      response_format: Json,
      path: "/api/v1/foo/1",
      base: variable("api-service"),
    }),
  )
  .addEdge(START, "create_foo")
  .addEdge("create_foo", "wait_between")
  .addEdge("wait_between", "get_foo")
  .addEdge("get_foo", END)
  .build();

export default monitor;

For assertion nodes in the graph builder, use Assertion(assertions) where assertions is an array built with Assert(...) and a state proxy (see createStateProxy in the assertions API). The sequential builder’s .assert(callback) is usually easier for assertion-heavy flows.

Using Secrets

Secrets are referenced in request config (e.g. headers or body) and resolved at runtime by the executor’s secret providers.

With Sequential Builder

import {
  GET,
  createMonitorBuilder,
  Json,
  Frequency,
  secret,
  Assert,
  variable,
} from "@griffin-app/griffin-core";

const monitor = createMonitorBuilder({
  name: "authenticated-check",
  frequency: Frequency.every(5).minute(),
})
  .request("protected", {
    method: GET,
    response_format: Json,
    path: "/api/protected",
    base: variable("api-service"),
    headers: {
      "X-API-Key": secret("API_KEY"),
      Authorization: secret("API_TOKEN"),
    },
  })
  .assert((state) => [Assert(state["protected"].status).equals(200)])
  .build();

export default monitor;

With Graph Builder

import {
  GET,
  createGraphBuilder,
  HttpRequest,
  START,
  END,
  Json,
  Frequency,
  secret,
  Assertion,
  createStateProxy,
  Assert,
  variable,
} from "@griffin-app/griffin-core";

const stateProxy = createStateProxy(["authenticated_request"]);
const monitor = createGraphBuilder({
  name: "authenticated-check",
  frequency: Frequency.every(5).minute(),
})
  .addNode(
    "authenticated_request",
    HttpRequest({
      method: GET,
      response_format: Json,
      path: "/api/protected",
      base: variable("api-service"),
      headers: {
        "X-API-Key": secret("API_KEY"),
        Authorization: secret("API_TOKEN"),
      },
    }),
  )
  .addNode(
    "verify",
    Assertion([Assert(stateProxy["authenticated_request"].status).equals(200)]),
  )
  .addEdge(START, "authenticated_request")
  .addEdge("authenticated_request", "verify")
  .addEdge("verify", END)
  .build();

export default monitor;

Secret API

  • secret(ref) – Reference by name (letters, numbers, underscore). Resolution (e.g. env vars, AWS Secrets Manager) is configured in the executor.

Variables

Use variable("key") for values (e.g. base URL) that are supplied per environment at runtime:

base: variable("api-service");

For a single variable as a string, use variable("key") directly. To build a string from multiple variables, secrets, or node refs, use the template tagged template literal (see below).

Templates

Use the template tagged template literal when path or base (or any string field that accepts refs) must combine multiple variables, secrets, or node refs into one value. Each interpolation must be a variable(), secret(), or a state proxy reference (e.g. state["node"].body["id"]).

import {
  GET,
  createMonitorBuilder,
  Json,
  Frequency,
  Assert,
  variable,
  secret,
  template,
} from "@griffin-app/griffin-core";

const monitor = createMonitorBuilder({
  name: "versioned-health",
  frequency: Frequency.every(1).minute(),
})
  .request("health", {
    method: GET,
    response_format: Json,
    path: template`/api/${variable("api-version")}/health`,
    base: template`https://${variable("env")}.api.example.com`,
    headers: {
      "X-API-Key": secret("API_KEY"),
    },
  })
  .assert((state) => [Assert(state["health"].status).equals(200)])
  .build();

Mixed variable and secret:

path: template`/api/${variable("env")}/data?key=${secret("API_KEY")}`;

In a request callback you can combine static parts with state refs:

.request("get_order", (state) => ({
  method: GET,
  response_format: Json,
  base: variable("api-service"),
  path: template`/api/v1/orders/${state["create_order"].body["order_id"]}`,
}))

Only variable(), secret(), and state proxy references are allowed inside template; plain strings or numbers will throw at build time.

API Reference

Sequential Builder

  • createMonitorBuilder(config)config: { name: string; frequency: Frequency; locations?: string[]; notifications?: MonitorNotification[] }
  • .request(name, config) – Add an HTTP request. config is either a static object or a callback (state) => config. Use the callback to reference previous responses: state["nodeName"].body["path"] or state["nodeName"].headers["headerName"] in path, base, headers, or body (body and headers only; status and latency cannot be referenced).
  • .wait(name, duration) – Add a wait. duration: WaitDuration.seconds(n), WaitDuration.minutes(n), or a number (milliseconds).
  • .assert(callback) – Add assertions. callback(state) returns an array of values from Assert(...).
  • .build() – Return the final MonitorDSL.

Graph Builder

  • createGraphBuilder(config) – Same config shape as sequential (name, frequency, optional locations, notifications).
  • .addNode(name, node) – Add a node. node is from HttpRequest(config), Wait(duration), or Assertion(assertions).
  • .addEdge(from, to) – Connect nodes. Use START and END for entry and exit.
  • .build() – Return the final MonitorDSL (validates that all nodes are connected).

Request Config (HttpRequest / .request)

  • methodGET, POST, PUT, DELETE, PATCH, etc.
  • path – Path string, variable("key"), template... (see Templates), or (in a request callback) a reference like state["node"].body["id"] or state["node"].headers["x-id"].
  • base – Base URL: string, variable("key"), template..., or a state reference in a callback.
  • response_formatJson, Xml (from @griffin-app/griffin-core).
  • headers – Optional record; values may use secret("...") or, in a callback, state references.
  • body – Optional body; may use secret("...") or, in a callback, state references (e.g. state["create_order"].body["order_id"]).

When you use a callback for .request(), any value that comes from the state proxy (e.g. state["node"].body["key"]) is serialized as a $nodeRef and resolved at execution time from the previous node’s response. Only body and headers of previous nodes can be referenced; status and latency cannot.

Wait Duration

  • WaitDuration.seconds(n) / WaitDuration.minutes(n) – From @griffin-app/griffin-core.
  • Wait(ms) – Raw milliseconds, e.g. Wait(2000).

Assertions

Use Assert() with the state proxy in .assert(callback) or when building an array for Assertion(...):

.assert((state) => [
  Assert(state["node"].status).equals(200),
  Assert(state["node"].latency).lessThan(500),
  Assert(state["node"].body["id"]).not.isNull(),
  Assert(state["node"].body["name"]).equals("test"),
  Assert(state["node"].headers["content-type"]).contains("json"),
])

See ASSERTIONS_QUICK_REF.md for the full assertion API.

General

  • FrequencyFrequency.every(n).minute(), .minutes(), .hour(), .hours(), .day(), .days().
  • Response formatsJson, Xml (exported as constants).
  • Schema / types – Import from @griffin-app/griffin-core/schema and @griffin-app/griffin-core/types as needed.

Output

Calling .build() returns a MonitorDSL object. Serialize it to JSON for the executor. Request nodes may contain $nodeRef in path, base, headers, or body when you use the callback form of .request(); the executor resolves these from previous responses at run time. Example shape:

{
  "name": "health-check",
  "version": "1.0",
  "frequency": {
    "every": 1,
    "unit": "MINUTE"
  },
  "nodes": [
    {
      "id": "health",
      "type": "HTTP_REQUEST",
      "method": "GET",
      "path": { "$literal": "/health" },
      "base": { "$variable": { "key": "api-service" } },
      "response_format": "JSON"
    }
  ],
  "edges": [
    { "from": "__START__", "to": "health" },
    { "from": "health", "to": "__END__" }
  ]
}