Jev Events

Anything else

Logs, queues, support tickets, webhooks and public firehoses. If it produces text, you can monitor it.

Jev Events isn't limited to its integrations. Anything that produces text over time can be a source, and your own functions handle what fires.

monitor.ts
import { from, monitor, noul, webhook } from "jev-events";

// Any async iterable: log lines, a queue, a database cursor.
const logs = monitor({
  source: from(logLines, { noun: "line" }),
  questions: { outage: noul("Does this log line describe a failure a human should look at?") },
}).on("outage", { min: 0.8 }, (e) => page(e.item.text));

// Or anything that can send a webhook: POST { "text": "…" } to http://127.0.0.1:8787.
const tickets = monitor({
  source: webhook({ port: 8787, secret: process.env.WEBHOOK_SECRET }),
  questions: { angry: noul("Is the customer angry?") },
}).on("angry", { min: 0.7 }, (e) => escalate(e.item.text));

await Promise.all([logs.start(), tickets.start()]);

Ideas

StreamSourceA question to ask
Application logsfrom(lines), or stdin in the CLIDoes this line describe a failure a human should look at?
Support ticketswebhook({ map }) from your help deskIs the customer angry? Is this a bug report?
Product reviewsfrom(rows) over a database cursorDoes this review mention a specific missing feature?
Contact formswebhook({ map }) from your backendIs this a sales lead, a support request or spam?
Blueskybluesky({ keywords }) from jev-events/publicIs this post about our product, and is it a complaint?

Try it without code

Pipe any text into the CLI, one item per line, and ask your own question:

tail -f app.log | npx jev-events watch stdin -a "outage=Does this line describe a failure a human should look at?"

Or start a webhook and POST { "text": "…" } to http://127.0.0.1:8787/:

npx jev-events watch webhook -a "angry=Is the customer angry?"

See the CLI for every source and flag.

From your own code

from(values) reads any iterable or async iterable: an array, a generator, a readline interface, a database cursor. The source ends when the iterable does, so await logs.run() judges everything, then returns counts of what was judged, dropped and done.

OptionDefaultWhat it does
mapStrings and { text } as they areTurns each value into an item. Return null to skip it
noun"item"What one item is called, so Jev reads "this line" or "this ticket"
recent0Preceding items to show Jev as context
id"custom:from"The source's id, which is also the monitor's unless you give it one

An item is a string, or an object with text and any of id, author, at, facts and raw. Missing ids and timestamps are filled in.

Over HTTP

webhook() turns anything that can send a webhook into a source. Started on its own, it listens on its own port. Passed to runtime() in a web app, it answers POSTs to /api/jev/webhook/<monitor id> instead.

OptionDefaultWhat it does
mapA string, { text } or an array of themTurns a request body into items. JSON bodies are parsed
secretNoneRequires Authorization: Bearer <secret>
port, host, path8787, "127.0.0.1", "/"Where it listens when started on its own. Use "0.0.0.0" to accept other machines
maxBytes1 MBThe largest body it accepts
id"webhook"The source's id, which is also the monitor's unless you give it one
tickets.ts
import { monitor, noul, webhook } from "jev-events";

const tickets = monitor({
  source: webhook<{ ticket: { id: string; body: string; requester: string } }>({
    secret: process.env.WEBHOOK_SECRET,
    map: ({ ticket }) => ({ id: ticket.id, text: ticket.body, author: ticket.requester }),
  }),
  questions: { angry: noul("Is the customer angry?") },
}).on("angry", { min: 0.7 }, (e) => console.log(`Ticket ${e.item.id} needs a person`));

await tickets.start();

Public streams

jev-events/public has read-only sources that need no sign-in, for demos, the CLI and trying questions on real traffic: twitchChat(channel) reads any public Twitch chat, and bluesky({ keywords, langs }) reads new Bluesky posts.

posts.ts
import { monitor, noul } from "jev-events";
import { bluesky } from "jev-events/public";

const posts = monitor({
  source: bluesky({ keywords: ["typescript"], langs: ["en"] }),
  questions: { help: noul("Is this post asking for help with a TypeScript problem?") },
}).on("help", (e) => console.log(e.item.text));

await posts.start();

Build an integration

The sources guide shows how a source checks, streams or receives webhooks, and defineAction() turns a platform API call into a native action with dry-run and protected users built in. Outlook, Teams, Discord, GitHub and more are on the roadmap.

On this page