Skip to content

metz-sh/simulacrum

Repository files navigation

Simulacrum

It's a react based code playground, where you can write TypeScript code and the bundled runtime will execute that code visually.
It's a self sustained package and doesn't need any server. All the compilation and execution happens in the browser itself

The complexity around designing a system or software is usually expressed through underwhelming tools. We are used to dragging and dropping boxes, or sometimes use code to automate that.

With simulacrum, you design using code. And you write code only to solve the design problem and nothing else.

demo.mp4

Try in Playground · Docs · Home

Mental Model

Say you are building a system which has a polling mechanism and it fetches data from one database and updates another. You shouldn't have to write code to create a box for a poller. You should be writing code FOR a poller.

So you might come up with a design like:

  1. For a particular condition, check if database contains what we are looking for. Update it and retrieve it.
  2. If we do get something from first step, add it to another database.

With metz, this is what the code for a poller will look like:

class Poller {
    private ordersTable = new OrdersTable();
    private pollingCollection = new PollingCollection();

    poll() {
        // Step 1
        const updatedOrder = this.ordersTable.updateOne(
            {
                status: 'processing'
            },
            order => order.status === 'pending'
        );

        // Step 2
        if(!updatedOrder) {
            return;
        }
	std.log("Got an order!")

	this.pollingCollection.insert({
            id: `poll_${updatedOrder.id}`,
            order_id: updatedOrder.id
        });
    }

}

And this is what how the runtime will visualise it:

poller.mp4

Features

  • The runtime executes your code on an internal tick, which essentially acts as time. So a method call might happen on tick=1 and a message might be logged at tick=2.
    • Which means you simulate hard to create scenarios, like multiple things happening at the same time.
  • Data gets rendered as a first class citizen. You can design your system along with the data.
    • More importantly, you can show how the data changes throughout a flow.
  • You don't just write code. You write stories to tell how the code should be executed.
    • Stories enable you to view your design from different vantage points.

There are so many more cool features. And you can find all of them in our documentation!

How does it work?

The IDE is backed by monaco and we run the typescript compiler using vfs on a web worker.

When compiling we run the code through our transformer, which in turn converts the code into state-machines.

To be precise, it converts your code into AsyncGenerators where the generator yields one of our instructions.

Sounds fancy, except our instructions are really simple:

export enum MethodRuntimeCommands {
	LOAD = 'load',
	LOG = 'log',
	UNLOAD = 'unload',
	HALT = 'halt',
	NO_OP = 'no_op',
	AWAIT_FLOW = 'await_flow',
}

In turn, the runtime manages all these state machines and their transitions. Take this code for example:

class Main {
    /**
     * Is reponsible for many important things.
    */
    hello() {
        const result = this.world('Hello');
        std.log(result);
    }

    /**
     * Does all the heavy lifting!
    */
    world(arg: string) {
        return `${arg} World!`
    }
}

Here's a simplistic view of what it looks like after transformation:

  flowchart TB
    subgraph hello
        direction TB
        load1[LOAD] --> log[LOG]
        log[LOG] --> unload1[UNLOAD]
    end
    subgraph world
        direction TB
        load2[LOAD: string] --> unload2[UNLOAD: string]
    end

    runtime --> hello
    runtime --> world
Loading

The runtime maintains a number internally, called currentTick. It also has a function called tick() whose job is to get all the available state machines, and make them transition, together.

When the combined transition of all the state machines is complete, currentTick is incremented. Here's the code if you want to check it out. Or you can read the docs for more.

How does it render?

The runtime comes with a heap and stack. Every object that your code creates is registered with the heap, and every method call is recorded on the stack. The visualisation is simply a rendering of how the heap and stack evolve. For example, if a method calls another, then it'd be reflected on the stack. And this particular state, will be rendered as the signal moving from one node to another on the playground.

Examples

How to set up?

Install all the dependencies using:

yarn install

And once done, you can use Storybook to start playing with the SDK. Simply run:

yarn storybook

This will start running the storybook app on port 6006. Head over to http://localhost:6006 to access all stories.

Off the shelf usage

You can use the app and embed your diagrams anywhere you want. For example:

<iframe
  width="1200"
  height="1200"
  src="https://app.metz.sh/play/0286b754d9e4408ba172e344eeac47b9">
</iframe>

Next Steps

Check out the docs to dig deep into the fundamental concepts and inner workings.

Feel free to join our slack if you have any question.

Or simply start playing with it in the playground!

About

Code-playground to visualise complex engineering flows.

Resources

License

Stars

Watchers

Forks

Releases

No releases published