@beardedtim/archie
TypeScript icon, indicating that this package has built-in type declarations

0.1.14 • Public • Published

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 git@github.com: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.

Readme

Keywords

none

Package Sidebar

Install

npm i @beardedtim/archie

Weekly Downloads

0

Version

0.1.14

License

MIT

Unpacked Size

122 kB

Total Files

109

Last publish

Collaborators

  • beardedtim