Skip to content

Lightweight actor library for Web Workers inspired by Akka

License

Notifications You must be signed in to change notification settings

pricingmonkey/tangi

Repository files navigation

👧 tangi

ತಂಗಿ [tangi] Kan. younger sister
ಅಕ್ಕ [akka] Kan. older sister

Lightweight actor library for Web Workers inspired by Akka.

What is this?

Type-safe, production-ready and lightweight messaging layer for Web Workers.

Best served with:

Why?

For people to scale Web Workers beyond the simple patterns of communication.

Basic usage

messages.ts

type PingMessage = {
  _tag: "PING";
}

type PongMessage = {
  _tag: "PONG";
}

main.ts

import { makeActorContext } from "tangi";
import { PingMessage, PongMessage } from "./messages";

const worker = new (require("worker-loader!./worker"))();
const workerRemoteContext = makeActorContext<PingMessage, never>(worker);
const response = await workerRemoteContext.ask<string, PongMessage>(id => ({ _tag: "PING", id }));
switch (response._tag) {
  case "Right": {  
    console.log(response.right);
  }
  case "Left": {  
    console.error(response.left);
  }
}

worker.ts

import { makeActorContext, REPLY } from "tangi";
import { PongMessage } from "./messages";

const workerLocalContext = makeActorContext<never, PongMessage>(globalThis as any);
workerLocalContext.receiveMessage(message => {
  switch (message._tag) {
    case "PING": {
      message[REPLY]({ _tag: "PONG" });
      return;
    }  
  }
});

Interaction patterns

Fire and forget

Use workerRemoteContext.tell({ _tag: "FIRE" }). See example below:

messages.ts

type PingMessage = {
  _tag: "PING";
}

type PongMessage = {
  _tag: "PONG";
}

main.ts

import { makeActorContext } from "tangi";

type FireMessage = {
  _tag: "FIRE";
}

const worker = new (require("worker-loader!./worker"))();
const workerRemoteContext = makeActorContext<FireMessage, never>(worker);
workerRemoteContext.tell({ _tag: "FIRE" });

Request-response

Use workerRemoteContext.ask({ _tag: "PING" }) combined with workerLocalContext.receiveMessage({ _tag: "PING" }). See example below:

messages.ts

type PingMessage = {
  _tag: "PING";
}

type PongMessage = {
  _tag: "PONG";
}

main.ts

import { makeActorContext } from "tangi";
import { PingMessage, PongMessage } from "./messages";

const worker = new (require("worker-loader!./worker"))();
const workerRemoteContext = makeActorContext<PingMessage, never>(worker);
const response = await workerRemoteContext.ask<string, PongMessage>(id => ({ _tag: "PING", id }));
console.log(response)

worker.ts

import { makeActorContext, REPLY } from "tangi";
import { PongMessage } from "./messages";

const workerLocalContext = makeActorContext<never, PongMessage>(globalThis as any);
workerLocalContext.receiveMessage(message => {
  switch (message._tag) {
    case "PING": {
      message[REPLY]({ _tag: "PONG" });
      return;
    }  
  }
});

Cancellation (single message)

worker.ts

import { makeActorContext, REPLY, makeCancellationOperator } from "tangi";

type PingMessage = {
  _tag: "PING";
  id: string;
}

type CancelMessage = {
  _tag: "CANCEL";
  id: string;
}

const makeTask = (killSwitch) => {
  return async () => {
    for (let i = 0; i < 100000; i++) {
      await fetch("http://example.org");
      if (killSwitch.isCancelled) {
        return;
      }
    }
  }
};

const workerLocalContext = makeActorContext<never, PongMessage>(globalThis as any);
const cancellationOperator = makeCancellationOperator();
workerLocalContext.receiveMessage(async message => {
  switch (message._tag) {
    case "PING": {
      const cancellableTask = cancellationOperator.register(message.id, message.id, makeTask);
      try {
        await task.promise();
      } finally {
        cancellationOperator.unregister(message.id, message.id);  
      }
      return;
    }
    case "CANCEL": {
      cancellationOperator.cancel(message.id);
      return;
    }
  }
});

Cancellation (message groups)

Use it to cancel multiple messages in a given group/context.

Similar to Cancellation (single message) and:

  • types become:
type PingMessage = {
  _tag: "PING";
  groupId: string;
  id: string;
}

type CancelMessage = {
  _tag: "CANCEL";
  groupId: string;
  id: string;
}
  • cancellation operator calls change to:
cancellationOperator.register(message.contextId, message.id, task);
      
cancellationOperator.unregister(message.contextId, message.id);
      
cancellationOperator.cancel(message.contextId);

License

Blue Oak Model License

About

Lightweight actor library for Web Workers inspired by Akka

Resources

License

Stars

Watchers

Forks

Packages

No packages published