
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@ezy/makina
Advanced tools
create a backend in your frontend
Writing frontend applications is difficult.
Makina may help you create an all-in-one backend for your frontend.
In Makina you should think of your frontend application as a complete stack, with your UI (React, Vue, Angular, etc...) as the frontend part and a StateMachine as a backend.
Application logic, I/O and state management of your application will be managed in the StateMachine while the UI will be responsible for presentational logic, displaying the current state and dispatch actions.
npm i @ezy/makina
createBase to create and combine modules.filters, to orchestrate them.index.ts
import { createBase } from '@ezy/makina';
interface UserProfile {
email: string;
}
interface CurrentUserState {
isConnecting?: boolean;
profile?: UserProfile;
error?: string;
}
interface Credentials {
email: string;
password: string;
}
interface CurrentUserIO {
api: {
login: (credentials: Credentials) => Promise<UserProfile>;
};
}
const CurrentUserBase = createBase({
states: {
CONNECTING: {
is: (state: CurrentUserState) => !!state.isConnecting,
set: (_: CurrentUserState) => ({ isConnecting: true }),
from: ['DISCONNECTED'],
},
CONNECTED: {
is: (state: CurrentUserState) => !!state.profile,
set: (_: CurrentUserState, profile: UserProfile) => ({ profile }),
from: ['CONNECTING'],
},
DISCONNECTED: {
is: (state: CurrentUserState) => !state.profile,
set: (_: CurrentUserState, error?: string) => ({ error }),
from: ['CONNECTING', 'CONNECTED'],
},
}
});
class CurrentUser extends CurrentUserBase<CurrentUserState, CurrentUserIO> {
/**
* connect currrent user
*
* @param credentials
*/
public async connect(credentials: Credentials) {
// transition to CONNECTING state
// there is no transition from CONNECTING to CONNECTING so double clicks are handled for us
if (this.to.CONNECTING()) {
try {
const profile = await this.IO.api.login(credentials);
return this.to.CONNECTED(profile);
} catch (e) {
this.to.DISCONNECTED(e);
}
}
return false;
}
/**
* disconnect currrent user
*/
public disconnect() {
return this.to.DISCONNECTED();
}
}
interface Notification {
id: number,
text: string
}
interface NotificationsState {
list: Notification[]
}
// create a Notifications module
class Notifications extends createBase()<NotificationsState> {
constructor(initialState, IO, options) {
// default state
super({ list: [], ...initialState }, IO, options)
}
add(notification) {
this.commit('notificationAdded', {
list: [...this.state.list, notification]
});
}
}
// define our app
const BaseApp = createBase({
modules: {
currentUser: CurrentUser,
notifications: Notifications
}
});
class App extends BaseApp {
init() {
// add a filter to the CurrentUser module connect method
// our filter will be triggered when the connect method is called
// to automatically create notification in our Notification module if neccessary
this.currentUser.connect.applyFilter(async (next, credentials) => {
if (await next(credentials)) {
this.notifications.add({
id: 0,
text: `welcome back ${this.currentUser.state.profile.email}`
});
return true
}
return false
});
}
}
// create an instance of our App with initial state and IOs
const app = App.create(
{},
{
api: {
login: (credentials: Credentials) => {
return Promise.resolve({ email: credentials.email });
}
}
}
);
/**
* wait for all init functions to be called
* not necessary in most cases but may be when unit testing
*/
// await app.ready;
// UI bindings
document
.getElementById('submit')
.addEventListener('click', () => {
app.currentUser.connect({
email: document.getElementById('email').value,
password: document.getElementById('password').value
})
});
document
.getElementById('logout')
.addEventListener('click', () => app.currentUser.disconnect());
const render = () => {
document.getElementById('notifications').innerHTML = `
<ul>${
app.notifications.state.list.map(msg => `<li>${msg.text}</li>`).join('')
}</ul>
`;
};
app.onStateChange(render);
render();
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>App</title>
</head>
<body>
<div id="app">
<button id="logout">X</button>
<div id="notifications"></div>
<form>
<label>Email : </label>
<input type="text" placeholder="Enter Email" id="email" required>
<label>Password : </label>
<input type="password" placeholder="Enter Password" id="password" required>
<button id="submit">Login</button>
</form>
</div>
<script src="index.js"></script>
</body>
</html>
Under the hood Makina keep your state in a single place and use lenses to update that state
class App extends createBase({ modules: { messages: Messages } })
is equivalent to
class App extends createBase()<{ messages: MessagesState }> {
public messages: Filterables<Messages> = this.create(lensProp('messages'), Messages);
// or
// public messages: Filterables<Messages> = this.create('messages', Messages);
}
create can be used to create state machines targeting just a part of your state on demand.
class App extends createBase({ modules: { messages: Messages } }) {
public getMessageByIndex(index) {
return this.create(lensPath(['messages', 'list', index]), Message);
}
}
alternatively the static create method can be used to create detached state machines
class App extends createBase({ modules: { messages: Messages } }) {
public getMessageByIndex(index) {
return Message.create(this.state.messages.list[index], this.IO)
}
}
lenses being a composable pair of pure getter and setter functions, Makina also handle split lenses which is just a pure getter and a pure setter in an object.
const nameSplitLens = {
get: (s) => s.name,
set: (name, s) => ({ ...s, name })
}
Makina provide a way to ensure your application state is never mutated. set in the config object a freeze function to deep freeze your state when a new state is created. Deep freezing objects can be costly from a performance standpoint, it's recommended to disable it in production.
import { config } from '@ezy/makina';
// immutable state in all environnments except production
config.freeze =
process.env.NODE_ENV !== 'production' ? require('deep-freeze-strict') : undefined;
Every plugin is a decorator factory and can be used to extends Classes generated by createBase.
myPlugin.ts
import { StateContainer, StateContainerClass } from '@ezy/makina';
declare class MyPlugin<S> extends StateContainer<S> {
public isPluginActive (): boolean;
}
declare module '@ezy/makina' {
interface Plugins<S> {
myPlugin: S extends { option1: boolean; }
? (options: { option1: boolean; }) => (Base: StateContainerClass) => typeof MyPlugin
: never;
}
}
export default {
name: 'myPlugin',
decoratorFactory: (options: { option1: boolean; }) =>
(Base: StateContainerClass) => {
return class extends Base {
public isPluginActive () {
return options.option1
}
} as any;
}
};
app.ts
import { install, createBase } from '@ezy/makina';
import plugin from './myPlugin'
install(plugin)
const Base = createBase({
myPlugin: {
option1: true
}
})
class Customized extends Base<{ somedata: boolean }> {
}
const custom = new Customized({ somedata: true })
// => true
custom.isPluginActive()
the plugin type is determined based on if the options argument is required
(options: { option1: boolean; }) => ... will only activate the plugin when the options argument is provided to createBase.
(options: { option1: boolean; } = { option1: false }) => ... will activate the plugin regardless if the options argument is provided to createBase or not.
FAQs
create a backend in your frontend
We found that @ezy/makina demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.