Try in Playground · Docs · Home · Wiki
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
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:
- For a particular condition, check if database contains what we are looking for. Update it and retrieve it.
- 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
- 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 attick=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!
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
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.
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.
- Hello world
- Simple auto scaler: Dynamically adds servers when demand surges.
- Average stock price: Uses event emitters to publish prices, and calculates the average in another component.
- An event bus: Show how you could design a central event bus in your own system.
- A polling setup: Shows how data interacts with parallel and scheduled flows.
- A system mid migration: Shows how you can
await
and compose flows.
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.
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>
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!