Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@persevie/statemanjs
Advanced tools
import { createState, createComputedState } from "@persevie/statemanjs";
type Planet = {
name: string;
system: string;
satelites: string[];
hasLife: boolean;
distance: number;
averageTemperature: number;
};
type Coordinates = {
latitude: number;
longitude: number;
};
type Rover = {
planet: string;
name: string;
days: number;
batteryCharge: number;
status: string;
weatherOutside: string;
coordinates: Coordinates;
};
const planetState = createState<Planet>({
name: "Earth",
system: "Solar System",
satelites: [],
hasLife: true,
distance: 1_000_000,
averageTemperature: 15,
});
const planetStateUnsub = planetState.subscribe((state) => {
console.log("Planet state updated:", state);
});
const planetStateDistanceUnsub = planetState.subscribe(
(state) => {
console.log("Planet state distance updated:", state.distance);
},
{
properties: ["distance"],
},
);
planetState.update((state) => {
state.satelites.push("Moon"); // <-- This will not trigger the planet state distance subscription
});
// --> Planet state updated: { name: 'Earth', system: 'Solar System', satelites: ["Moon"], hasLife: true, distance: 1000000 }
planetState.update((state) => {
state.distance = 224_000_900; // <-- This will trigger the planet state distance subscription
});
// --> Planet state updated: { name: 'Earth', system: 'Solar System', satelites: ["Moon"], hasLife: true, distance: 224000900 }
// --> Planet state distance updated: 224000900
planetState.set({
name: "Mars",
system: "Solar System",
satelites: ["Phobos", "Deimos"],
hasLife: false,
distance: 100,
averageTemperature: -63,
}); // <-- This will trigger both planet state distance and planet state subscription
// --> Planet state updated: { name: 'Mars', system: 'Solar System', satelites: ["Phobos", "Deimos"], hasLife: false, distance: 100, averageTemperature: -63 }
// --> Planet state distance updated: 100
planetStateUnsub(); // <-- Unsubscribe from planet state
planetStateDistanceUnsub(); // <-- Unsubscribe from planet state distance
const marsExplorerState = createState<Rover>({
planet: "Mars",
name: "MarsExplorer",
days: 0,
batteryCharge: 100,
status: "On the way",
weatherOutside: "unknown",
coordinates: {
latitude: 0,
longitude: 0,
},
});
function generateReport(state: StatemanjsAPI<Rover>): string {
return `Rover report state updated. My status is ${
state.get().status
}. I'm on day ${state.get().days}. My battery charge is ${
state.get().batteryCharge
}. Weather outside is ${state.get().weatherOutside}. My coordinates are ${
state.get().coordinates.latitude
}, ${state.get().coordinates.longitude}.
My coordinates are: lat ${state.get().coordinates.latitude}, long ${
state.get().coordinates.longitude
}.
The weather outside is: ${state.get().weatherOutside}.`;
}
const marsExplorerDaysState = marsExplorerState.createSelector(
(state) => state.days,
);
marsExplorerDaysState.subscribe((state) => {
console.log("MarsExplorer Days state updated:", state);
});
const marsExplorerReportState = createComputedState<string>((): string => {
return generateReport(marsExplorerState);
}, [marsExplorerState]); // <-- State of report. Generate mars explorer report state every MarsExplorerState change
marsExplorerReportState.subscribe((state) => {
console.log(state);
});
marsExplorerState.set({
planet: "Mars",
name: "MarsExplorer",
days: 10,
batteryCharge: 85,
status: "Active",
weatherOutside: "Sunny",
coordinates: {
latitude: 4.5,
longitude: 137.4,
},
});
// --> Rover report state updated. My status is Active. I'm on day 10. My battery charge is 85. Weather outside is Sunny.
// --> MarsExplorer Days state updated: 10
marsExplorerState.subscribe(
() => {
charge(marsExplorerState);
},
{ notifyCondition: (s): boolean => s.batteryCharge < 10 },
);
function charge(roverState: StatemanjsAPI<Rover>) {
roverState.asyncAction(async (state: StatemanjsAPI<Rover>) => {
console.log("Charging the rover...");
await new Promise((resolve) => setTimeout(resolve, 10000));
state.update((state) => {
state.batteryCharge = 100;
});
});
}
marsExplorerState.set({
planet: "Mars",
name: "MarsExplorer",
days: 8,
batteryCharge: 0,
status: "Inactive",
weatherOutside: "Sunny",
coordinates: {
latitude: -14.6,
longitude: 130.7,
},
});
// --> Charging the rover...
// 10s waiting
// --> Rover report state updated. My status is Inactive. I'm on day 8. My battery charge is 100. Weather outside is Sunny.
Statemanjs is a framework-agnostic library for creating and managing the state of your JavaScript and NodeJS applications.
Key features:
Any manipulations with your state are possible only through built-in methods, so they should be understandable and convenient.
The createState
method is used to create a state:
createState<T>(element: T): StatemanjsAPI<T>;
StatemanjsAPI<T>
/**
* Accepts a new state and compares it with the current one.
* Nothing will happen if the passed value is equal to the current one.
* @param newState New state.
* @returns Status of operation.
*/
set(newState: T): boolean;
/** Get current state */
get(): T;
/**
* The method of subscribing to the status change.
* Accepts a callback function (subscription callback),
* which will be called at each update, and a subscription options object.
* In the options, you can specify information about the subscription,
* as well as specify the condition under which the subscriber will be notified
* and mark the subscriber as protected. All subscribers are unprotected by default.
* Protected subscribers can only be unsubscribed using the unsubscribe method returned by this method.
* Returns the unsubscribe callback function.
*
* @param subscriptionCb A function that runs on every update.
* @param subscriptionOptions Additional information and notification condition.
* @returns Unsubscribe callback function.
*/
subscribe(
subscriptionCb: SubscriptionCb<T>,
subscriptionOptions?: SubscriptionOptions<T>,
): UnsubscribeCb;
/** Remove all unprotected subscribers */
unsubscribeAll(): void;
/**
* Returns count of all active subscribers.
* @returns number.
*/
getActiveSubscribersCount(): number;
/**
* Flexible state update.
* @param updateCb Callback for state updates.
*/
update(updateCb: UpdateCb<T>, currentState?: T): void;
/**
* Unwrap a proxy object to a regular JavaScript object
* @returns unwrapped state
*/
unwrap(): T;
/**
* Dispatch an async action
* @param action An async action. It accepts a stateManager object,
* which is used to access the current state.
* @returns Promise.
*/
asyncAction(
action: (stateManager: StatemanjsAPI<T>) => Promise<void>,
): Promise<void>;
/**
* Create a computed state for a state property.
* @param selectorFn A function that returns a value of a state property.
* @returns A computed state.
*/
createSelector<E>(selectorFn: (state: T) => E): StatemanjsComputedAPI<E>;
/**
* Debug API. Allows you to use additional debugging functionality such as transactions.
* Parameters are set when creating the state.
* @see {DebugAPI}
*/
DEBUG?: DebugAPI<T>;
The createComputedState
method is used to create a computed state:
createComputedState<T>(callback: () => T, deps: (StatemanjsAPI<any> | StatemanjsComputedAPI<any>)[]): StatemanjsComputedAPI<T>
StatemanjsComputedAPI<T>
/** Get current state */
get(): T;
/**
* The method of subscribing to the status change.
* Accepts a callback function (subscription callback),
* which will be called at each update, and a subscription options object.
* In the options, you can specify information about the subscription,
* as well as specify the condition under which the subscriber will be notified
* and mark the subscriber as protected. All subscribers are unprotected by default.
* Protected subscribers can only be unsubscribed using the unsubscribe method returned by this method.
* Returns the unsubscribe callback function.
*
* @param subscriptionCb A function that runs on every update.
* @param subscriptionOptions Additional information and notification condition.
* @returns Unsubscribe callback function.
*/
subscribe(
subscriptionCb: SubscriptionCb<T>,
subscriptionOptions?: SubscriptionOptions<T>,
): UnsubscribeCb;
/** Remove all unprotected subscribers */
unsubscribeAll(): void;
/**
* Returns count of all active subscribers.
* @returns number.
*/
getActiveSubscribersCount(): number;
/**
* Unwrap a proxy object to a regular JavaScript object
* @returns unwrapped state
*/
unwrap(): T;
TransactionAPI<T>
/**
* Number of transactions since state initialization
*/
totalTransactions: number;
/**
* Add transaction to the chain
* @param snapshot
*/
addTransaction(snapshot: T): void;
getLastTransaction(): Transaction<T> | null;
getAllTransactions(): Transaction<T>[];
getTransactionByNumber(transactionNumber: number): Transaction<T> | null;
getLastDiff(): TransactionDiff<T> | null;
getDiffBetween(
transactionA: number,
transactionB: number,
): TransactionDiff<T> | null;
DebugAPI<T>
transactionService: TransactionAPI<T>;
A state can be anything from primitives to complex and multidimensional objects. Just pass this to the createState
function and use the state with no extra effort.
const isLoading = createState(true);
const soComplexObject = createState({
1: { 2: { 3: { 4: { 5: [{ foo: "bar" }] } } } },
});
npm i @persevie/statemanjs
To use Statemanjs, you'll need to create a state object and interact with it using the provided API methods.
Here's an example of creating a state object for storing a user's name:
import { createState } from "@persevie/statemanjs";
const userState = createState({ name: "Jake" });
You can also pass in the type of your state if you are using TypeScript:
import { createState } from "@persevie/statemanjs";
type User = {
name: string;
age: number;
};
const userState = createState<User>({ name: "Finn", age: 13 });
To get the current state, use the get
method.
const counterState = createState(1);
const counter = counterState.get(); // 1
The subscribe
method takes a callback function and executes it on every state change. This callback function accepts the updated state.
const counterState = createState(0);
// the 'state' parameter is the updated (current) state
counterState.subscribe((state) => {
if (Number.isInteger(state)) {
console.log("it's integer");
} else {
console.log("it's not integer");
}
});
You can set a condition, notifyCondition
, under which the callback will be called. This condition is the second and optional parameter. If there is no condition, then the callback will fire on every state change. notifyCondition
also accepts the updated state.
const counterState = createState(0);
counterState.subscribe(
(state) => {
console.log("it's integer");
},
{ notifyCondition: (state) => Number.isInteger(state) },
);
To protect a subscriber - pass protect: true
to the second argument of the object. Protected subscribers can only be unsubscribed using the unsubscribe method returned by the subscribe
method.
const counterState = createState(0);
counterState.subscribe(
(state) => {
console.log("it's integer");
},
{ notifyCondition: (state) => Number.isInteger(state), protect: true },
);
You can specify which properties you want the subscriber to be notified when they change (at least one). If none of the properties have been changed, the subscriber will not be notified. Note that the set
method always replaces the state, so use the update
method to observe the properties correctly. Set is set.
const userState = createState({
name: "Jake",
surname: "Dog",
info: { hobbies: [] },
});
userState.subscribe(
(state) => {
console.log(`The name has been changed: ${state.name}`);
},
{ properties: ["name"] },
);
userState.subscribe(
(state) => {
console.log(
`Hobbies have been changed: ${state.info.hobbies.join(", ")}`,
);
},
{ properties: ["info.hobbies"] },
);
The subscribe
method returns a callback to unsubscribe.
const counterState = createState(0);
const unsub = counterState.subscribe(
(state) => {
console.log("it's integer");
},
{ notifyCondition: (state) => Number.isInteger(state) },
);
// cancel subscribe
unsub();
To unsubscribe all active and unprotected subscriptions from a state, use the unsubscribeAll
method;
counterState.unsubscribeAll();
Sometimes you need to find out how many active subscriptions a state has, for this there is a getActiveSubscribersCount
method.
const subscribersCount = counterState.getActiveSubscribersCount();
There are two ways to change the state - set
and update
. The set
method completely changes the state and is great for primitives and simple states.
const counterState = createState(0);
counterState.subscribe(
(state) => {
console.log("it's integer");
},
{ notifyCondition: (state) => Number.isInteger(state) },
);
counterState.set(2); // 2
counterState.set(counterState.get() * 2); // 4
The update
method is suitable for complex states (objects and arrays) in which only part of the state needs to be changed. The update
method accepts the current state.
import { createState } from "@persevie/statemanjs";
type User = {
name: string;
age: number;
isOnline: boolean;
hobbyes: Array<string>;
};
const userState = createState<User>({
name: "Finn",
age: 13,
isOnline: false,
hobbyes: [],
});
userState.update((state) => {
state.isOnline = !state.isOnline;
});
userState.update((state) => {
state.hobbyes.push("adventure");
});
If you want unwrap state to javascript object - use unwrap()
method:
import { createState } from "@persevie/statemanjs";
type User = {
name: string;
age: number;
isOnline: boolean;
hobbyes: Array<string>;
};
const userState = createState<User>({
name: "Finn",
age: 13,
isOnline: false,
hobbyes: [],
});
const unwrappedUser = userState.unwrap();
You can create a computed state with the createComputedState
function. It returns an instance of statemanjs, but without the ability to set or update the state because of its specificity (see the StatemanjsComputedAPI
interface).
This function takes two parameters:
Computed state creates only protected subscribers.
const problemState = createState<boolean>(false);
const statusComputedState = createComputedState<string>((): string => {
return problemState.get()
? "Houston, we have a problem"
: "Houston, everything is fine";
}, [problemState]);
You can create a selector for a state object to track changes only to it. A selector is a computed state, but only for the current state and its property.
const state = createState({ count: 0, value: 42 });
state.subscribe((newState) => {
console.log("State changed:", newState);
});
const countSelector = state.createSelector(
(currentState) => currentState.count,
);
countSelector.subscribe((newCount) => {
console.log("Count changed:", newCount);
});
If you need to change state asynchronously, for example to set data from an api call, you can use the asyncAction
method. It takes a callback function with a state instance as a parameter.
const state = createState({ count: 0, value: 0 });
state.subscribe((newState) => {
console.log("State changed:", newState);
});
state.asyncAction(async (stateManager) => {
await new Promise((resolve) => setTimeout(resolve, 10000));
stateManager.update((s) => {
s.count++;
});
});
const arrState = createState([], { transactionsLen: 10 });
const gat = () => arrState.DEBUG.transactionService.getAllTransactions();
arrState.subscribe((state) => {
console.log("diff: ", arrState.DEBUG.transactionService.getLastDiff());
});
arrState.set([0, 1]);
const arr = [
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
async function te() {
for (let index = 0; index < arr.length; index++) {
const element = arr[index];
await new Promise((resolve) => setTimeout(resolve, 1000));
arrState.update((s) => {
s.push(element);
});
}
}
te().then(() => {
console.log(
"all transactions: ",
arrState.DEBUG.transactionService.getAllTransactions(),
);
// -->
// diff: null
// diff: { old: [ 0, 1 ], new: [ 0, 1, 2 ] }
// diff: { old: [ 0, 1, 2 ], new: [ 0, 1, 2, 3 ] }
// diff: { old: [ 0, 1, 2, 3 ], new: [ 0, 1, 2, 3, 4 ] }
// diff: { old: [ 0, 1, 2, 3, 4 ], new: [ 0, 1, 2, 3, 4, 5 ] }
// ...
// all transactions: [
// {
// number: 11,
// snapshot: [
// 0, 1, 2, 3, 4,
// 5, 6, 7, 8, 9,
// 10, 11
// ],
// timestamp: 1702588027170
// },
// {
// number: 12,
// snapshot: [
// 0, 1, 2, 3, 4, 5,
// 6, 7, 8, 9, 10, 11,
// 12
// ],
// timestamp: 1702588028173
// },
// {
// number: 13,
// snapshot: [
// 0, 1, 2, 3, 4, 5,
// 6, 7, 8, 9, 10, 11,
// 12, 13
// ],
// timestamp: 1702588029175
// },
// {
// number: 14,
// snapshot: [
// 0, 1, 2, 3, 4, 5,
// 6, 7, 8, 9, 10, 11,
// 12, 13, 14
// ],
// timestamp: 1702588030176
// },
// {
// number: 15,
// snapshot: [
// 0, 1, 2, 3, 4, 5,
// 6, 7, 8, 9, 10, 11,
// 12, 13, 14, 15
// ],
// timestamp: 1702588031179
// },
// {
// number: 16,
// snapshot: [
// 0, 1, 2, 3, 4, 5, 6,
// 7, 8, 9, 10, 11, 12, 13,
// 14, 15, 16
// ],
// timestamp: 1702588032180
// },
// {
// number: 17,
// snapshot: [
// 0, 1, 2, 3, 4, 5, 6,
// 7, 8, 9, 10, 11, 12, 13,
// 14, 15, 16, 17
// ],
// timestamp: 1702588033183
// },
// {
// number: 18,
// snapshot: [
// 0, 1, 2, 3, 4, 5, 6,
// 7, 8, 9, 10, 11, 12, 13,
// 14, 15, 16, 17, 18
// ],
// timestamp: 1702588034187
// },
// {
// number: 19,
// snapshot: [
// 0, 1, 2, 3, 4, 5, 6,
// 7, 8, 9, 10, 11, 12, 13,
// 14, 15, 16, 17, 18, 19
// ],
// timestamp: 1702588035189
// },
// {
// number: 20,
// snapshot: [
// 0, 1, 2, 3, 4, 5, 6,
// 7, 8, 9, 10, 11, 12, 13,
// 14, 15, 16, 17, 18, 19, 20
// ],
// timestamp: 1702588036193
// }
// ]
});
The examples of storage implementations for each state-manager (except statemanjs) were taken from the official documentation of these libraries.
One by one adds n
elements to the array x
times. Where n
is a number from the array of numbers [1, 10, 100, 1000, 10000, 100000, 1000000, 2000000, 5000000, 10000000,
50000000] (countOfElements), and x
is the number of iterations (1 by default). If n = 5; x = 2
, that means to add 5
elements 2
times. The element
is an object {foo: "bar", baz: "qux"}
. Between iterations the storage is reset (empty array).
The average value for iterations is calculated and written as the result.
Think of this case as a TODO list with a simple structure, e.g. {title: string, notes: string}
.
The benchmark was run on a MacBook Pro m1 16gb.
You can run the benchmarks on your computer. You can also add new benchmarks or modify existing ones. Read more about it here.
Below is a table with the results of the fill benchmark.
time in
ms
❌ - means an error during execution or too long execution time (>6h).
Items | effector | mobx | redux | statemanjs |
---|---|---|---|---|
1 | 0.010970029979944229 | 0.01990541983395815 | 0.0040803998708724976 | 0.0020753702148795126 |
10 | 0.04626586981117725 | 0.11000874035060405 | 0.014035369530320167 | 0.010449579730629922 |
100 | 0.17841962995938956 | 0.4354520997777581 | 0.08275457009673119 | 0.06232665043324232 |
1000 | 1.208628780017607 | 2.586632479839027 | 0.8747471100464463 | 0.2421091901510954 |
10000 | 58.332799129989 | 31.700192469991745 | 52.266411220021546 | 2.2227349602803588 |
100000 | 13849.532463340052 | 322.1863979200646 | 12867.839250005782 | 27.505533350259064 |
1000000 | 2448118.7541659996 | 4473.258667119965 | 2354867.223542001 | 279.83934087000785 |
2000000 | ❌ | 9588.994868720061 | ❌ | 605.3742875201627 |
5000000 | ❌ | ❌ | ❌ | 1468.102162090242 |
10000000 | ❌ | ❌ | ❌ | 3185.2785096402094 |
50000000 | ❌ | ❌ | ❌ | 14499.883542001247 |
Statemanjs has significantly better performance than others. This suggests that Statemanjs may be a good choice for state management in JavaScript applications that need to perform many updates on large data sets in a short period of time. It may also be a good choice for applications that need to perform updates on complex data structures, as Statemanjs is able to handle these updates more efficiently.
Statemanjs is framework agnostic and can be used without additional packages. But for convenience, there are packages for the most popular frameworks - react, vue, solid. Statemanjs supports svelte out of the box and doesn't need any additional packages. To work with additional packages, the main statemanjs package is required.
See CONTRIBUTING.md.
FAQs
Proper state manager for JavaScript
We found that @persevie/statemanjs demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers 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
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.