@quanta-lib/q-state 1.0.1
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 +167 -0
- package/dist/Q-State-Engine.js +55 -0
- package/package.json +25 -0
- package/src/Q-State-Engine.ts +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Q-State
|
|
2
|
+
|
|
3
|
+
A lightweight React state management library using `useSyncExternalStore` with optional localStorage caching and value transformation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @quanta-lib/q-state
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Type-safe state management** - Full TypeScript support with generic types
|
|
13
|
+
- **useSyncExternalStore** - Uses React's built-in concurrent-safe subscription mechanism
|
|
14
|
+
- **Value transformation** - Transform values during updates with custom transformer functions
|
|
15
|
+
- **LocalStorage caching** - Persist state to localStorage with optional caching
|
|
16
|
+
- **Lightweight** - Minimal dependencies, no context providers needed
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Q_StateEngine } from "@q-state/core"
|
|
24
|
+
|
|
25
|
+
const qstate = new Q_StateEngine({
|
|
26
|
+
count: 0,
|
|
27
|
+
name: 'Amarix'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export function App() {
|
|
31
|
+
const [count] = qstate.useQuantaState('count')
|
|
32
|
+
const [name] = qstate.useQuantaState('name')
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<h1>Counter App</h1>
|
|
37
|
+
<p>Name: {name}</p>
|
|
38
|
+
<p>Count: {count}</p>
|
|
39
|
+
<button onClick={() => qstate.updateValue('count', (prev) => prev + 1)}>
|
|
40
|
+
Increment
|
|
41
|
+
</button>
|
|
42
|
+
</>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### With Value Transformer
|
|
48
|
+
|
|
49
|
+
Transform values automatically when updating state:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const qstate = new Q_StateEngine({
|
|
53
|
+
name: 'Amarix'
|
|
54
|
+
}, {
|
|
55
|
+
// Transformer function runs after updateValue
|
|
56
|
+
name: (raw) => {
|
|
57
|
+
console.log(raw) // Logs the new value
|
|
58
|
+
return raw.toUpperCase() // Transform to uppercase
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// When updating name, the transformer will be applied
|
|
63
|
+
qstate.updateValue('name', () => 'hello') // Stores 'HELLO'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With LocalStorage Caching
|
|
67
|
+
|
|
68
|
+
Enable caching to persist state across page reloads:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const qstate = new Q_StateEngine({
|
|
72
|
+
count: 0,
|
|
73
|
+
name: 'Amarix'
|
|
74
|
+
}, {
|
|
75
|
+
name: (raw) => console.log(raw)
|
|
76
|
+
}, {
|
|
77
|
+
cache: true // Enable localStorage caching
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Nested Updates
|
|
82
|
+
|
|
83
|
+
You can update multiple state values in a single update function:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
<button onClick={() => qstate.updateValue('name', () => {
|
|
87
|
+
qstate.updateValue('count', () => 5) // Nested update
|
|
88
|
+
return 'helo :3'
|
|
89
|
+
})}>
|
|
90
|
+
Update Name
|
|
91
|
+
</button>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API
|
|
95
|
+
|
|
96
|
+
### Constructor
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
new Q_StateEngine<T, O>(state: T, transformer?: O, option?: OptionRecord)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **state** (T): Initial state object with type safety
|
|
103
|
+
- **transformer** (O, optional): Record of transformer functions for specific keys
|
|
104
|
+
- **option** (OptionRecord, optional): Configuration options
|
|
105
|
+
- `cache`: Enable localStorage caching (default: false)
|
|
106
|
+
|
|
107
|
+
### Methods
|
|
108
|
+
|
|
109
|
+
#### `updateValue(key, func)`
|
|
110
|
+
|
|
111
|
+
Updates a state value using an updater function.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
qstate.updateValue('count', (prev) => prev + 1)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- **key**: The state key to update
|
|
118
|
+
- **func**: Updater function that receives the current value and returns the new value
|
|
119
|
+
|
|
120
|
+
#### `useQuantaState(key)`
|
|
121
|
+
|
|
122
|
+
React hook for subscribing to state changes.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const [value] = qstate.useQuantaState('key')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Returns a tuple with the current value. The component re-renders when the specified key changes.
|
|
129
|
+
|
|
130
|
+
## TypeScript
|
|
131
|
+
|
|
132
|
+
The library provides full type safety:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
interface AppState {
|
|
136
|
+
count: number
|
|
137
|
+
name: string
|
|
138
|
+
items: string[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const qstate = new Q_StateEngine<AppState>({
|
|
142
|
+
count: 0,
|
|
143
|
+
name: '',
|
|
144
|
+
items: []
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Type-safe updates
|
|
148
|
+
qstate.updateValue('count', (prev) => prev + 1) // ✓
|
|
149
|
+
qstate.updateValue('name', () => 'Hello') // ✓
|
|
150
|
+
qstate.updateValue('items', (prev) => [...prev, 'new']) // ✓
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## How It Works
|
|
154
|
+
|
|
155
|
+
1. **State Storage**: State is stored in a private object within the Q_StateEngine instance
|
|
156
|
+
2. **Subscription**: Uses React's `useSyncExternalStore` for concurrent-safe subscriptions
|
|
157
|
+
3. **Updates**: When `updateValue` is called, it:
|
|
158
|
+
- Executes the updater function with the current value
|
|
159
|
+
- Runs the transformer function (if defined)
|
|
160
|
+
- Updates the internal state
|
|
161
|
+
- Persists to localStorage (if caching enabled)
|
|
162
|
+
- Notifies all subscribers
|
|
163
|
+
4. **Caching**: On initial render, checks localStorage for cached values and hydrates state
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Q_StateEngine = void 0;
|
|
4
|
+
var react_1 = require("react");
|
|
5
|
+
var Q_StateEngine = /** @class */ (function () {
|
|
6
|
+
function Q_StateEngine(state, transformer, option) {
|
|
7
|
+
var _this = this;
|
|
8
|
+
this.subsribe = function (cb) {
|
|
9
|
+
_this.listener.add(cb);
|
|
10
|
+
return function () { return _this.listener.delete(cb); };
|
|
11
|
+
};
|
|
12
|
+
this.updateValue = function (key, func) {
|
|
13
|
+
var _a;
|
|
14
|
+
var executeFunc = func(_this.obj[key]);
|
|
15
|
+
var transformData = (_this.transformer && _this.transformer[key])
|
|
16
|
+
? _this.transformer[key](executeFunc)
|
|
17
|
+
: executeFunc;
|
|
18
|
+
// Kita ambil saja data hasil update jika data dari layer transformer nya undefined
|
|
19
|
+
var finalData = transformData !== null && transformData !== void 0 ? transformData : executeFunc;
|
|
20
|
+
_this.obj[key] = finalData;
|
|
21
|
+
if ((_a = _this.option) === null || _a === void 0 ? void 0 : _a.cache)
|
|
22
|
+
localStorage.setItem(key, JSON.stringify(_this.obj[key]));
|
|
23
|
+
_this.listener.forEach(function (cb) { return cb(); });
|
|
24
|
+
};
|
|
25
|
+
this.useQuantaState = function (key) {
|
|
26
|
+
var getCurr = function () {
|
|
27
|
+
var _a;
|
|
28
|
+
if (((_a = _this.option) === null || _a === void 0 ? void 0 : _a.cache) && typeof window !== "undefined") {
|
|
29
|
+
var getcache = localStorage.getItem(key);
|
|
30
|
+
if (getcache !== null && getcache !== 'undefined') {
|
|
31
|
+
var dataFromCache = '';
|
|
32
|
+
try {
|
|
33
|
+
dataFromCache = JSON.parse(getcache);
|
|
34
|
+
}
|
|
35
|
+
catch (_b) {
|
|
36
|
+
dataFromCache = _this.obj[key];
|
|
37
|
+
}
|
|
38
|
+
if (dataFromCache !== _this.obj[key]) {
|
|
39
|
+
_this.obj[key] = dataFromCache;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return _this.obj[key];
|
|
44
|
+
};
|
|
45
|
+
var syncSpecificData = (0, react_1.useSyncExternalStore)(_this.subsribe, getCurr);
|
|
46
|
+
return [syncSpecificData];
|
|
47
|
+
};
|
|
48
|
+
this.obj = state;
|
|
49
|
+
this.transformer = transformer;
|
|
50
|
+
this.listener = new Set();
|
|
51
|
+
this.option = option;
|
|
52
|
+
}
|
|
53
|
+
return Q_StateEngine;
|
|
54
|
+
}());
|
|
55
|
+
exports.Q_StateEngine = Q_StateEngine;
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quanta-lib/q-state",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A lightweight React state management library using `useSyncExternalStore` with optional localStorage caching and value transformation.",
|
|
5
|
+
"main": "dist/Q-State-Engine.js",
|
|
6
|
+
"types": "src/Q-State-Engine.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"state management",
|
|
16
|
+
"global state",
|
|
17
|
+
"quanta",
|
|
18
|
+
"state"
|
|
19
|
+
],
|
|
20
|
+
"author": "quanta-lib",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react"
|
|
2
|
+
|
|
3
|
+
interface OptionRecord {
|
|
4
|
+
cache: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class Q_StateEngine
|
|
8
|
+
<T extends Record<string, any>,
|
|
9
|
+
O extends Partial<Record<keyof T, (raw: any) => any>>>
|
|
10
|
+
{
|
|
11
|
+
private listener: Set<() => void>
|
|
12
|
+
private obj: T // Type-safe Map sesuai skema T
|
|
13
|
+
private transformer: O
|
|
14
|
+
private option?: OptionRecord
|
|
15
|
+
|
|
16
|
+
constructor(state: T, transformer?: O, option?: OptionRecord) {
|
|
17
|
+
this.obj = state
|
|
18
|
+
this.transformer = transformer as any
|
|
19
|
+
this.listener = new Set()
|
|
20
|
+
this.option = option
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private subsribe = (cb: any) => {
|
|
24
|
+
this.listener.add(cb)
|
|
25
|
+
return () => this.listener.delete(cb)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
updateValue = <K extends keyof T>(key: K, func: (prev: T[K]) => T[K]) => {
|
|
29
|
+
const executeFunc = func(this.obj[key])
|
|
30
|
+
const transformData = (this.transformer && this.transformer[key])
|
|
31
|
+
? this.transformer[key](executeFunc)
|
|
32
|
+
: executeFunc
|
|
33
|
+
// Kita ambil saja data hasil update jika data dari layer transformer nya undefined
|
|
34
|
+
const finalData = transformData ?? executeFunc
|
|
35
|
+
this.obj[key] = finalData
|
|
36
|
+
if(this.option?.cache) localStorage.setItem(key as string, JSON.stringify(this.obj[key]))
|
|
37
|
+
this.listener.forEach(cb => cb())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
useQuantaState = <K extends keyof T>(key: K): [T[K]] => {
|
|
41
|
+
const getCurr = () => {
|
|
42
|
+
if (this.option?.cache && typeof window !== "undefined") {
|
|
43
|
+
const getcache = localStorage.getItem(key as string)
|
|
44
|
+
if (getcache !== null && getcache !== 'undefined') {
|
|
45
|
+
let dataFromCache = ''
|
|
46
|
+
try {
|
|
47
|
+
dataFromCache = JSON.parse(getcache)
|
|
48
|
+
} catch {
|
|
49
|
+
dataFromCache = this.obj[key]
|
|
50
|
+
}
|
|
51
|
+
if (dataFromCache !== this.obj[key]) {
|
|
52
|
+
this.obj[key] = dataFromCache as any
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return this.obj[key]
|
|
57
|
+
}
|
|
58
|
+
const syncSpecificData = useSyncExternalStore(
|
|
59
|
+
this.subsribe,
|
|
60
|
+
getCurr
|
|
61
|
+
)
|
|
62
|
+
return [syncSpecificData]
|
|
63
|
+
}
|
|
64
|
+
}
|