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

async-actors

v0.0.1

Published

Actors library for TypeScript and JavaScript

Downloads

3

Readme

Async actors

This is a research prototype. Please see the main GitHub page.

A library for building and testing applications using actors in TypeScript/JavaScript.

$ npm install async-actors

Features

The library provide the following key features:

  • TypeScript and JavaScript APIs for building applications using typed actors and simple actors.
  • A testing runtime that uses systematic concurrency testing to control all asynchrony and explore unexpected interleavings, exposing concurrency heisenbugs before release.

The library only provides messaging between actors within the same process; interprocess messaging must be handled by the developer.

Typed actors

The typed actors API enables developers to write actors using interfaces. Each actor has a FIFO queue of messages to handle; each message will be handled by the actor sequentially (one-after-the-other). When handling a message, an actor can create other actors and/or send messages to other actors and/or update its own state (i.e. its fields).

For example, in TypeScript:

// A file downloader actor that will be given a "goal"
// (a list of files to download).
export interface FileDownloaderActor {
  sendUpdateGoal(files: string[]): void;
  sendDownloadDone(filename: string): void;
  sendQueryLocalFSResult(localFiles: string[]): void;
}

Each method of the interface is a message type that it can handle. We can implement the interface/actor to define how the messages will be handled:

export class FileDownloaderActorImpl implements FileDownloaderActor {
  private goal: string[];
  private currentFiles: Set<string>;

  public sendUpdateGoal(files: string[]): void {
    this.goal = files;
    // TODO: Send message to query local FS.
  }

  public sendDownloadDone(_: string): void {
    // TODO: Send message to query local FS.
  }

  public sendQueryLocalFSResult(localFiles: string[]): void {
    this.currentFiles = new Set(localFiles);
    this.resolve();
  }

  private resolve() {
    // TODO: Download missing files.
  }
}

We can create this actor and send it various messages:

// Create the actor and get a reference to it.
const actorRef =
  typedRuntime.create<FileDownloaderActor>(new FileDownloaderActorImpl());

// These "method calls" actually just send a message to the actor.
// The messages will be handled by the file downloader actor 
// at some point in the future.
// The "method calls" return immediately, 
// regardless of whether the message has been handled yet.
actorRef.sendUpdateGoal(["one.txt", "two.txt"]);
actorRef.sendQueryLocalFSResult(["one.txt", "three.txt"]);

Notice that the methods all have a void return type, as they correspond to sending messages that will be processed asynchronously. The API also supports synchronous-style messages using async and await. For example, consider a file system actor:

// A file system actor that can query the file system.
export interface FileSystemActor {
  listFiles(path: string): Promise<string[]>;
  getFileContents(file: string): Promise<string>;
}

We can send a listFiles message and wait for the result (just like a normal method call):

// Create the actor and get a reference to it.
const actorRef =
  typedRuntime.create<FileSystemActor>(/*...omitted...*/);

const files: string[] = await actorRef.listFiles(".");
// Do something with files. E.g.
const contents: string = await actorRef.getFileContents(files[0]);

Synchronous-style methods are those that do not start with "send" (e.g. listFiles vs. sendUpdateGoal) and must have a Promise<...> return type. Exceptions are also propagated.

Although these look like normal async method calls, the continuation (the execution state after an await) is always executed/continued in the context of the calling actor and other messages cannot be handled by the actor in the meantime (although this behaviour can be changed via an option). This unfortunately means that deadlock is possible via a cyclic chain of calls.

The typed actors API is built on top of the simple actors API.

Simple actors

The simple actors API enables developers to write more traditional, simple actors by implementing the Actor interface:

export interface Actor<T> {
  handle(msg: T): void;
}

For example:

// Define a union type of all messages we want to handle.
type DMsg =
  GoalMsg
| DownloadDoneMsg
| QueryLocalFSResult;


// Define a file downloader actor, similar to earlier.
class FileDownloaderSimpleActor implements Actor<DMsg> {

  private goal: string[];
  private currentFiles: Set<string>;

  public handle(msg: DMsg) {

    if (msg instanceof GoalMsg) {
      this.goal = msg.files; 
      // ...
    } else if (msg instanceof DownloadDoneMsg) {
      this.currentFiles.add(msg.filename); 
      // ...
    } else {
      this.currentFiles = new Set(msg.localFiles);
      // ...
    }
  }
}

Notice that, due to type narrowing, the TypeScript compiler (and autocomplete tooling) narrows the type of msg within the if/else blocks.

Here is how we create the actor and send messages to it:

const downloaderActor =
  runtime.create("FileDownloaderSimpleActor", new FileDownloaderSimpleActor());

downloaderActor.send(new GoalMsg(["1.txt", "2.txt"]));

The testing runtime is implemented at the level of simple actors; testing of typed actors comes for free.

Testing

The library also supports thorough testing of different interleavings via the testing runtime. For example, the following TypeScript test harness aims to test what happens when a file is renamed at the same time as a file server actor tries to list the files in a directory:

class TestRenaming implements Harness {
  public async main(/* ...omitted... */): Promise<void> {
    
    /* ... */
    
    // Server actor that can list the files in a directory.
    const serverActor =
      typedRuntime.create(/*...*/);
    
    // Renamer actor that renames `test.txt` to `test1.txt`.
    const renamerActor = 
      typedRuntime.create(/*...*/);
    
    // Rename the file at some point in the future.
    renamerActor.sendGo();

    // List the (virtual) directory `testdata`
    const dir = await fsServerActor.loadDirectoryInfo("testdata");
    
  }
}

The file server first obtains a list of files and then queries information about each file before returning the result. If the file is renamed between the list and query then an error will occur. This is very unlikely to happen when running normally. However, when using the testing runtime, the error is seen every time because all possible interleavings are explored.

See test.ts for the full test harness.

FAQ

Please see the main GitHub page