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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@beardedtim/archie

v0.1.14

Published

A way to build _processors_ that _do things_ and result in _some context_ being set across them.

Downloads

63

Readme

Archie

A way to build processors that do things and result in some context being set across them.

Usage

You can find full-fledged examples inside of /examples if you want to see how this might look in real-life

PING -> PONG

Here is an example of a system sending messages to itself in order to respond to a specific question. This proves that each invocation is a new closure/ran each time and that the systems can be nested.

enum Actions {
  PING = "PING",
  PONG = "PONG",
}

RootSystem.when(Actions.PING)
  .validate(always(Promise.resolve(true)))
  .do(async (ctx, action) => {
    const pingSymbol = Symbol("New Ping");
    const result = await RootSystem.handle(Actions.PONG, {
      id: pingSymbol,
    });

    ctx.set("body", result.get("body") === pingSymbol);
  });

RootSystem.when(Actions.PONG)
  .validate(always(Promise.resolve(true)))
  .do(async (ctx, action) => {
    ctx.set("body", action.payload.id);
  });

const pingSuccessful = await RootSystem.handle(Actions.PING, {});

console.log(pingSuccessful.get("body")); // true

Express Integration

/**
 * You can manually handle the triggering of the system
 * via some external event, such as an Express Request Handler
 */
const healthcheckRouter: RequestHandler = async (req, res, next) => {
  const ctx = await System.handle(
    Actions.HEALTHCHECK,
    Plugins.Express.actionFromRequest(req).payload
  );

  res.json(ctx.get("body"));
};

/**
 * And attach it to the external system manuall
 */
Server.get("/healthcheck", healthcheckRouter);

/**
 * Or you can use a plugin and just have the System
 * generically handle the external request
 */
Server.use(Plugins.Express.middleware(System));

System.when("/:foo")
  .where(async (action) => action.payload.method === "get")
  .do(async (ctx, action) => {
    console.log("I am doing anything that starts with /:foo and is a GET request", action.meta);
  });

Nested Systems

import { System, Log } from "@beardedtim/archie";
import { randomUUID } from "crypto";

const CruddySystem = new System({
  name: "Cruddy",
  usePattern: true,
  // ignore validation for demo purposes
  useManualActionValidation: true
});

const UserSystem = new System({
  name: "Cruddy::User",
  // ignore validation for demo purposes
  useManualActionValidation: true
});

UserSystem.when("CREATE").do(async (ctx, action) => {
  if (!action.payload.body) {
    console.log(action, "action");
    throw new Error("Missing Body for Creating of User");
  }

  ctx.set("body", {
    id: "123-456",
    name: action.payload.body.name,
  });
});

CruddySystem.beforeAll().do(async (ctx, action) => {
  const traceId = randomUUID({ disableEntropyCache: true });

  ctx.set("trace-id", traceId);

  Log.trace({ action, traceId }, "Handling");
});

CruddySystem.afterAll().do(async (ctx, action) => {
  const traceId = ctx.get("trace-id");

  Log.trace({ action, traceId }, "Handled");
});

CruddySystem.when("foobar").do(async (ctx, action) => {
  Log.debug({ action }, "I am the event handler");

  // Systems can call Systems
  const result = await UserSystem.handle("CREATE", {
    body: {
      name: "Tim",
    },
  });

  ctx.set("body", {
    data: result.get("body"),
  });
});

const main = async () => {
  const result = await CruddySystem.handle("foobar", {
    some: "payload",
  });

  console.log(result.get("body"), result.get("trace-id"));
};

main();

Helpers

validateByJSONSchema

This allows you to say that the action.payload value will match a specific JSON Schema

const healthcheckSchema = {
  type: "object",
  required: ["hello"],
  properties: {
    hello: {
      type: "string",
    },
  },
};

/**
 * When some ACTION occours
 */
System.when(Actions.HEALTHCHECK)
  .validate(Helpers.validateByJSONScema(healthcheckSchema))
  /**
   * Do some list of things
   */
  .do(async (ctx, action) => {
    console.log("System handling HEALTHCHECK action", action);
    console.log("Maybe we go out and check Database connections, or whatever");

    ctx.set("body", {
      data: {
        healthy: true,
      },
    });
  });

Generating Docs

A System comes built with the ability to have docs generated for it. Right now, the docs are sparse but the interfaces are built and ready for consuming.

import { Doc } from '@beardedtim/archie'

/**
 * Somehow build up a UserSystem
 */
  const UserDocs = new Doc(UserSystem);
  console.log(UserDocs.generate());
  console.log();

prints something like

# System: User

- Uses Patterns:  No


## Actions

- Action CREATE
    - validateUserCreation
    - createUser
  

- Action READ_ONE
    - readUserById

to the console.

NOTE If you do not name the functions you pass to do, they will be a blank line. Always make the function you pass to do a named function (const foo = () => { ... }) if you intend on using the Documentation Generation in any serious way.

Demo

git clone [email protected]:beardedtim/archie.git

npm i

yarn ts-node demo/index.ts

Reasoning

I wanted a way to build an action processor that held some state between the pipeline processors. I also wanted to remove any concept of HTTP/WS/Whatever from the unit of value that I think action processing offers.

Instead of tying the middleware processor to some HTTP library like Express, I am trying to build our system in a way that is agnostic from the outside world in a meaningful way. I am trying to keep the unit of value as the modification of context given some action and action handlers.

Terminology

Action

An Action is an internal representation of some request or external thing that can be handled within the system.

Context

A Context is some shared state between Action Handlers

Action Handler

An Action Handler is responsible for taking in the tuple (Context, Action) and doing something with that, including setting some shared value on Context.

System

A System is a grouping of Action Handlers, Preware, and Postware. It is responsible for taking in some request, processing it, and returning some output.

Plugins

A way to interact with external things such as Express in an abstract way.