@lavaz/store 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @lavaz/store 🚀
2
+
3
+ ![Lavaz Store Logo](https://github.com/huylongnguyendev/lavaz-store/blob/main/assets/logo-80.png)
4
+
5
+ A lightweight, high-level state management infrastructure for React and JavaScript applications. Designed for complex workspaces, not just simple kanbans.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@lavaz/store/beta)](https://www.npmjs.com/package/@lavaz/store)
8
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@lavaz/store)](https://bundlephobia.com/package/@lavaz/store)
9
+
10
+ ## ✨ Features
11
+
12
+ - **Ultra Lightweight**: Less than 3kB gzipped.
13
+ - **Type-safe**: Built with TypeScript for excellent developer experience.
14
+ - **High Performance**: Optimized with `useSyncExternalStore` and shallow comparison to prevent unnecessary re-renders.
15
+ - **Zero Configuration**: Easy to set up and integrate into any project.
16
+
17
+ ## 📦 Installation
18
+
19
+ ```bash
20
+ pnpm add @lavaz/store
21
+ # or
22
+ npm install @lavaz/store
23
+ # or
24
+ yarn add @lavaz/store
25
+ # or
26
+ bun add @lavaz/store@beta
27
+ ```
28
+
29
+ # 🚀 Quick Start
30
+
31
+ ## 1. Create a Box
32
+ A "Box" is where your state and actions live.
33
+
34
+ ```bash
35
+ import { createBox } from "@lavaz/store";
36
+
37
+ export const counterBox = createBox({
38
+ state: { count: 0 },
39
+ actions: (set) => ({
40
+ inc: () => set((s) => ({ count: s.count + 1 })),
41
+ dec: () => set((s) => ({ count: s.count - 1 })),
42
+ }),
43
+ });
44
+ ```
45
+
46
+ ## 2. Create a store
47
+ A "Store" is the central hub that manages multiple boxes. It provides a unified interface to access your state and actions across the entire workspace.
48
+
49
+ ```bash
50
+ import { createStore } from "@lavaz/store";
51
+ import { authBox } from "./boxes/authBox";
52
+ import { projectBox } from "./boxes/projectBox";
53
+
54
+ // Initialize your global workspace store
55
+ export const store = createStore({
56
+ auth: authBox,
57
+ projects: projectBox,
58
+ // You can add more boxes as your workspace grows
59
+ });
60
+ ```
61
+
62
+ ## 3. Use in React
63
+ Use the `useAppStore` hook with a selector for optimal performance.
64
+
65
+ ```bash
66
+ import { useAppStore } from "@lavaz/store";
67
+ import { counterBox } from "./counterBox";
68
+
69
+ const Counter = () => {
70
+ // Only re-renders if 'count' changes
71
+ const [count, { inc, dec }] = useAppStore(counterBox, (s) => s.count);
72
+
73
+ return (
74
+ <div>
75
+ <h1>{count}</h1>
76
+ <button onClick={inc}>+</button>
77
+ <button onClick={dec}>-</button>
78
+ </div>
79
+ );
80
+ };
81
+ ```
82
+
83
+ ## 🛠 Advanced: Shallow Comparison
84
+ Lavaz automatically performs a shallow check on the data returned by your selector.
85
+
86
+ ```bash
87
+ const [user] = useAppStore(userBox, (s) => ({
88
+ name: s.name,
89
+ age: s.age
90
+ }));
91
+ ```
92
+
93
+ # 📄 License
94
+ MIT © [Nguyen Huy Long/Lavaz Ecosystem]
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var S=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),d= exports.validateState =(e,r,o,s=new WeakSet)=>{if(e===void 0)throw new Error(`[Lavaz]: ${r} cannot be undefined.`);if(typeof e=="function")throw new Error(`[Lavaz]: ${r} cannot be a function.`);if(S(e)){if(s.has(e))return;s.add(e);let t=Object.keys(e);if(o&&t.length===0)throw new Error("[Lavaz]: `initialState` cannot be an empty object.");for(let i of t)d(e,`${r}.${i}`,!1,s)}Array.isArray(e)&&e.forEach((t,i)=>{d(t,`${r}[${i}]`,!1,s)})};var v=(e,r)=>{let o=typeof process<"u"&&process.env.NODE_ENV==="production"||typeof import.meta<"u"&&import.meta.env.NODE_ENV==="production",s=typeof process<"u"&&(process.env.NODE_ENV==="test"||!!process.env.VITEST),t=null,i={persist:(n,c="local")=>(t={name:n,storage:c},i),create:()=>{(!o||s)&&d(e,"initialState",!0);let n=e,c=new Set,f=t?`lavaz-${t.name}`:null,p=_optionalChain([t, 'optionalAccess', _ => _.storage])==="session"?window.sessionStorage:window.localStorage;if(typeof window<"u"&&f)try{let a=p.getItem(f);a&&(n=JSON.parse(a))}catch (e2){o||console.warn(`[Lavaz]: Hydration failed for ${f}`)}let l=r(a=>{let u=typeof a=="function"?a(n):a;if(u===void 0)throw new Error("[Lavaz]: Cannot update `state` to `undefined`.");Object.is(n,u)||(n=u,f&&p.setItem(f,JSON.stringify(n)),c.forEach(m=>m(n)))});return{getState:()=>n,dispatch:l,subscribe:a=>(c.add(a),()=>c.delete(a)),boxId:_optionalChain([t, 'optionalAccess', _2 => _2.name])||`box-${Math.random().toString(36).substring(2,9)}`}}};return i};var $=e=>{let r=typeof process<"u"&&process.env.NODE_ENV==="production"||typeof import.meta<"u"&&import.meta.env.NODE_ENV==="production",o=typeof process<"u"&&(process.env.NODE_ENV==="test"||!!process.env.VITEST);if(!r||o){if(Object.keys(e).length===0)throw new Error("[Lavaz]: `store` cannot be an empty object.");let t=new Map;for(let[i,n]of Object.entries(e)){let c=t.get(n.boxId);if(c)throw new Error(`[Lavaz]: Conflict. The box you are trying to assign to "${i}" is already used by "${c}`);t.set(n.boxId,i)}}return e};var _react = require('react');var y=(e,r)=>{if(Object.is(e,r))return!0;if(typeof e!="object"||e===null||typeof r!="object"||r===null)return!1;let o=Object.keys(e),s=Object.keys(r);if(o.length!==s.length)return!1;for(let t=0;t<=o.length;t++)if(!Object.prototype.hasOwnProperty.call(r,o[t])||Object.is(e[o[t]],r[o[t]]))return!1;return!0};var z=(e,r=o=>o)=>{let o=_react.useRef.call(void 0, void 0),s=_react.useCallback.call(void 0, ()=>{let n=r(e.getState());return o.current===void 0||!y(o,n)?(o.current=n,n):o.current},[r,e]),t=_react.useSyncExternalStore.call(void 0, e.subscribe,s,s),i=_react.useMemo.call(void 0, ()=>e.dispatch,[e.dispatch]);return[t,i]};exports.createBox = v; exports.createStore = $; exports.shallowCompare = y; exports.useAppStore = z; exports.validateState = d;
@@ -0,0 +1,30 @@
1
+ type PrevFn<S> = (prev: S) => S;
2
+ type Next<S> = S | PrevFn<S>;
3
+ type Subscribe<S> = (cb: (state: S) => void) => () => void;
4
+ interface CreateBox<S = unknown, A = unknown> {
5
+ getState: () => S;
6
+ dispatch: A;
7
+ subscribe: Subscribe<S>;
8
+ boxId: string;
9
+ }
10
+ interface BoxBuilder<S, A> {
11
+ persist: (name: string, storage?: "local" | "session") => BoxBuilder<S, A>;
12
+ create: () => CreateBox<S, A>;
13
+ }
14
+
15
+ declare const createBox: <S, A>(initialState: S, actions: (set: (next: Next<S>) => void) => A) => BoxBuilder<S, A>;
16
+
17
+ declare const createStore: <T extends Record<string, CreateBox>>(boxes: T) => T;
18
+
19
+ declare const useAppStore: <S, A, R>(box: CreateBox<S, A>, selector?: (state: S) => R) => [R, A];
20
+
21
+ declare const validateState: (val: any, path: string, isRoot: boolean, seen?: WeakSet<WeakKey>) => void;
22
+
23
+ declare const shallowCompare: (prevObj: any, newObj: any) => boolean;
24
+
25
+ interface Persistance {
26
+ name: string;
27
+ storage?: "local" | "session";
28
+ }
29
+
30
+ export { type BoxBuilder, type CreateBox, type Next, type Persistance, type PrevFn, type Subscribe, createBox, createStore, shallowCompare, useAppStore, validateState };
@@ -0,0 +1,30 @@
1
+ type PrevFn<S> = (prev: S) => S;
2
+ type Next<S> = S | PrevFn<S>;
3
+ type Subscribe<S> = (cb: (state: S) => void) => () => void;
4
+ interface CreateBox<S = unknown, A = unknown> {
5
+ getState: () => S;
6
+ dispatch: A;
7
+ subscribe: Subscribe<S>;
8
+ boxId: string;
9
+ }
10
+ interface BoxBuilder<S, A> {
11
+ persist: (name: string, storage?: "local" | "session") => BoxBuilder<S, A>;
12
+ create: () => CreateBox<S, A>;
13
+ }
14
+
15
+ declare const createBox: <S, A>(initialState: S, actions: (set: (next: Next<S>) => void) => A) => BoxBuilder<S, A>;
16
+
17
+ declare const createStore: <T extends Record<string, CreateBox>>(boxes: T) => T;
18
+
19
+ declare const useAppStore: <S, A, R>(box: CreateBox<S, A>, selector?: (state: S) => R) => [R, A];
20
+
21
+ declare const validateState: (val: any, path: string, isRoot: boolean, seen?: WeakSet<WeakKey>) => void;
22
+
23
+ declare const shallowCompare: (prevObj: any, newObj: any) => boolean;
24
+
25
+ interface Persistance {
26
+ name: string;
27
+ storage?: "local" | "session";
28
+ }
29
+
30
+ export { type BoxBuilder, type CreateBox, type Next, type Persistance, type PrevFn, type Subscribe, createBox, createStore, shallowCompare, useAppStore, validateState };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var S=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),d=(e,r,o,s=new WeakSet)=>{if(e===void 0)throw new Error(`[Lavaz]: ${r} cannot be undefined.`);if(typeof e=="function")throw new Error(`[Lavaz]: ${r} cannot be a function.`);if(S(e)){if(s.has(e))return;s.add(e);let t=Object.keys(e);if(o&&t.length===0)throw new Error("[Lavaz]: `initialState` cannot be an empty object.");for(let i of t)d(e,`${r}.${i}`,!1,s)}Array.isArray(e)&&e.forEach((t,i)=>{d(t,`${r}[${i}]`,!1,s)})};var v=(e,r)=>{let o=typeof process<"u"&&process.env.NODE_ENV==="production"||typeof import.meta<"u"&&import.meta.env.NODE_ENV==="production",s=typeof process<"u"&&(process.env.NODE_ENV==="test"||!!process.env.VITEST),t=null,i={persist:(n,c="local")=>(t={name:n,storage:c},i),create:()=>{(!o||s)&&d(e,"initialState",!0);let n=e,c=new Set,f=t?`lavaz-${t.name}`:null,p=t?.storage==="session"?window.sessionStorage:window.localStorage;if(typeof window<"u"&&f)try{let a=p.getItem(f);a&&(n=JSON.parse(a))}catch{o||console.warn(`[Lavaz]: Hydration failed for ${f}`)}let l=r(a=>{let u=typeof a=="function"?a(n):a;if(u===void 0)throw new Error("[Lavaz]: Cannot update `state` to `undefined`.");Object.is(n,u)||(n=u,f&&p.setItem(f,JSON.stringify(n)),c.forEach(m=>m(n)))});return{getState:()=>n,dispatch:l,subscribe:a=>(c.add(a),()=>c.delete(a)),boxId:t?.name||`box-${Math.random().toString(36).substring(2,9)}`}}};return i};var $=e=>{let r=typeof process<"u"&&process.env.NODE_ENV==="production"||typeof import.meta<"u"&&import.meta.env.NODE_ENV==="production",o=typeof process<"u"&&(process.env.NODE_ENV==="test"||!!process.env.VITEST);if(!r||o){if(Object.keys(e).length===0)throw new Error("[Lavaz]: `store` cannot be an empty object.");let t=new Map;for(let[i,n]of Object.entries(e)){let c=t.get(n.boxId);if(c)throw new Error(`[Lavaz]: Conflict. The box you are trying to assign to "${i}" is already used by "${c}`);t.set(n.boxId,i)}}return e};import{useCallback as g,useMemo as x,useRef as h,useSyncExternalStore as w}from"react";var y=(e,r)=>{if(Object.is(e,r))return!0;if(typeof e!="object"||e===null||typeof r!="object"||r===null)return!1;let o=Object.keys(e),s=Object.keys(r);if(o.length!==s.length)return!1;for(let t=0;t<=o.length;t++)if(!Object.prototype.hasOwnProperty.call(r,o[t])||Object.is(e[o[t]],r[o[t]]))return!1;return!0};var z=(e,r=o=>o)=>{let o=h(void 0),s=g(()=>{let n=r(e.getState());return o.current===void 0||!y(o,n)?(o.current=n,n):o.current},[r,e]),t=w(e.subscribe,s,s),i=x(()=>e.dispatch,[e.dispatch]);return[t,i]};export{v as createBox,$ as createStore,y as shallowCompare,z as useAppStore,d as validateState};
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@lavaz/store",
3
+ "version": "1.0.2",
4
+ "description": "",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "type": "module",
9
+ "sideEffects": false,
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "scripts": {
14
+ "clean": "rimraf dist",
15
+ "test": "vitest",
16
+ "dev": "tsup --watch",
17
+ "build": "npm run clean && tsup src/index.ts --format esm,cjs --dts --clean --external react --minify --splitting",
18
+ "release:patch": "npm run build && npm version patch && npm publish --access public --tag beta"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "require": "./dist/index.cjs"
25
+ }
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0",
29
+ "react-dom": ">=18.0.0"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "keywords": [],
37
+ "author": "Nguyen Huy Long",
38
+ "license": "ISC",
39
+ "packageManager": "pnpm@10.32.1",
40
+ "devDependencies": {
41
+ "@testing-library/jest-dom": "^6.9.1",
42
+ "@testing-library/react": "^16.3.2",
43
+ "@types/node": "^25.5.0",
44
+ "@types/react": "^19.2.14",
45
+ "@types/react-dom": "^19.2.3",
46
+ "@vitejs/plugin-react": "^6.0.1",
47
+ "jsdom": "^29.0.0",
48
+ "react": "^19.2.4",
49
+ "react-dom": "^19.2.4",
50
+ "rimraf": "^6.1.3",
51
+ "rslib": "^0.0.10",
52
+ "tsc-alias": "^1.8.16",
53
+ "tsup": "^8.5.1",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.1.0"
56
+ }
57
+ }