@medyll/idae-stator 0.45.0 → 0.51.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/LICENSE +21 -21
- package/README.md +122 -92
- package/cli.js +46 -0
- package/dist/stator/Stator.d.ts +26 -12
- package/dist/stator/Stator.js +146 -78
- package/package.json +15 -11
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 medyll
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 medyll
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,92 +1,122 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
# @medyll/idae-stator ⚡
|
|
2
|
+
|
|
3
|
+
**idae-stator** is a lightweight, universal, and deeply reactive state management library for JavaScript and TypeScript. It uses native `Proxy` to track changes at any depth and provides a unified `EventTarget` API for both Browser and Node.js environments.
|
|
4
|
+
|
|
5
|
+
## 🚀 Key Features
|
|
6
|
+
|
|
7
|
+
* **Deep Reactivity:** Automatically tracks mutations in nested objects and arrays.
|
|
8
|
+
* **Zero Dependencies:** Extremely small footprint.
|
|
9
|
+
* **Universal (Isomorphic):** Works out of the box in the Browser, Node.js, and SSR environments.
|
|
10
|
+
* **Flexible API:** Support for both standard `onchange` callbacks and `addEventListener` patterns.
|
|
11
|
+
* **Developer Friendly:** Seamless integration with TypeScript, providing full intellisense for your state structures.
|
|
12
|
+
* **Smart Updates:** Built-in protection against redundant notifications for primitive values.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @medyll/idae-stator
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🛠 Usage
|
|
26
|
+
|
|
27
|
+
### Basic Reactivity
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { stator } from '@medyll/idae-stator';
|
|
31
|
+
|
|
32
|
+
const state = stator({ count: 0 });
|
|
33
|
+
|
|
34
|
+
state.onchange = (newValue) => {
|
|
35
|
+
console.log(`Count updated to: ${newValue.count}`);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
state.count++; // Console: Count updated to: 1
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Deep Nesting & Arrays
|
|
43
|
+
|
|
44
|
+
The library handles deep structures without any extra configuration.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const appState = stator({
|
|
48
|
+
user: {
|
|
49
|
+
profile: { name: 'Mydde' },
|
|
50
|
+
tags: ['dev', 'js']
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
appState.onchange = (val) => console.log('State changed!');
|
|
55
|
+
|
|
56
|
+
// All these mutations trigger the reactivity:
|
|
57
|
+
appState.user.profile.name = 'Mydde Dev';
|
|
58
|
+
appState.user.tags.push('stator');
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Event-Driven Pattern
|
|
63
|
+
|
|
64
|
+
If you prefer standard web events or need multiple listeners:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const state = stator({ status: 'idle' });
|
|
68
|
+
|
|
69
|
+
state.addEventListener('change', (event) => {
|
|
70
|
+
const { newValue } = event.detail;
|
|
71
|
+
console.log('New status:', newValue.status);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
state.status = 'loading';
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Type Safety (TypeScript)
|
|
79
|
+
|
|
80
|
+
**idae-stator** preserves your types and augments them with reactive properties.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
interface User {
|
|
84
|
+
id: number;
|
|
85
|
+
role: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const state = stator<User>({ id: 1, role: 'admin' });
|
|
89
|
+
// 'state' is now typed as AugmentedState<User>
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## ⚙️ Technical Overview
|
|
96
|
+
|
|
97
|
+
### The Reactivity Engine
|
|
98
|
+
|
|
99
|
+
The core of the library is built around a recursive `Proxy` system. When you access a nested object, **idae-stator** wraps it on-the-fly (and caches it using a `WeakMap`) to ensure that every leaf of your data tree becomes an observable entry point.
|
|
100
|
+
|
|
101
|
+
### Universal Event Bus
|
|
102
|
+
|
|
103
|
+
To ensure compatibility with Node.js (where `EventTarget` was historically missing or inconsistent), **idae-stator** implements a smart polyfill:
|
|
104
|
+
|
|
105
|
+
1. **Browser:** Uses native `window.EventTarget`.
|
|
106
|
+
2. **Legacy/DOM:** Uses an invisible DOM element as an event bridge.
|
|
107
|
+
3. **Node.js:** Uses a custom high-performance event registry.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 💎 Utility Properties
|
|
112
|
+
|
|
113
|
+
* **`state.stator`**: Access the raw data directly.
|
|
114
|
+
* **`state.toString()`**: Returns a `JSON.stringify` version of your state.
|
|
115
|
+
* **`state.valueOf()`**: Returns the underlying value for comparisons.
|
|
116
|
+
* **`Symbol.toPrimitive`**: Allows the state to be used directly in arithmetic (e.g., `state + 1`).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 📜 License
|
|
121
|
+
|
|
122
|
+
MIT © [Medyll](https://github.com/medyll)
|
package/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const pkgJson = require(path.join(__dirname, 'package.json'));
|
|
8
|
+
const packageName = pkgJson.name.replace(/^@[^/]+\//, '');
|
|
9
|
+
const cmd = process.argv[2];
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if (cmd === 'get-readme') {
|
|
13
|
+
const readmePath = path.join(__dirname, 'README.md');
|
|
14
|
+
if (fs.existsSync(readmePath)) {
|
|
15
|
+
const content = fs.readFileSync(readmePath, 'utf8');
|
|
16
|
+
console.log(content);
|
|
17
|
+
} else {
|
|
18
|
+
console.error('README.md not found in this package.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
} else if (cmd === 'install-skill') {
|
|
22
|
+
const readline = require('readline');
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout
|
|
26
|
+
});
|
|
27
|
+
const skillSrc = path.join(__dirname, 'SKILL.md');
|
|
28
|
+
const skillDest = path.resolve(__dirname, `../../../.github/skills/${packageName}/SKILL.md`);
|
|
29
|
+
if (!fs.existsSync(skillSrc)) {
|
|
30
|
+
console.error('SKILL.md not found in this package.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
rl.question(`This will copy SKILL.md to .github/skills/${packageName}/SKILL.md. Continue? (y/n): `, (answer) => {
|
|
34
|
+
if (answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes') {
|
|
35
|
+
fs.mkdirSync(path.dirname(skillDest), { recursive: true });
|
|
36
|
+
fs.copyFileSync(skillSrc, skillDest);
|
|
37
|
+
console.log(`SKILL.md installed to .github/skills/${packageName}/SKILL.md`);
|
|
38
|
+
} else {
|
|
39
|
+
console.log('Operation cancelled.');
|
|
40
|
+
}
|
|
41
|
+
rl.close();
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
console.log('Usage: <cli> get-readme | install-skill');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
package/dist/stator/Stator.d.ts
CHANGED
|
@@ -1,32 +1,46 @@
|
|
|
1
|
-
/** Represents primitive types
|
|
2
|
-
|
|
1
|
+
/** * Represents JavaScript primitive types
|
|
2
|
+
*/
|
|
3
|
+
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* For primitives, it wraps the value in an object
|
|
6
|
-
* For non-primitives, it keeps the original type
|
|
5
|
+
* Basic state wrapper structure
|
|
7
6
|
*/
|
|
8
7
|
type State<T> = {
|
|
9
8
|
value: T;
|
|
10
9
|
};
|
|
11
10
|
/**
|
|
12
11
|
* Callback function type for state changes
|
|
13
|
-
* @param oldValue The previous value of the state
|
|
14
|
-
* @param newValue The new value of the state
|
|
15
12
|
*/
|
|
16
|
-
type StateChangeHandler<T> = (
|
|
13
|
+
type StateChangeHandler<T> = (newValue: T) => void;
|
|
17
14
|
/**
|
|
18
|
-
* Augmented state type
|
|
15
|
+
* Augmented state type providing reactivity, event subscription, and utility methods
|
|
19
16
|
*/
|
|
20
17
|
type AugmentedState<T> = State<T> & {
|
|
18
|
+
/** The callback triggered on any state mutation */
|
|
21
19
|
onchange?: StateChangeHandler<T>;
|
|
20
|
+
/** Standard event listener for 'change' events */
|
|
21
|
+
addEventListener: (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => void;
|
|
22
|
+
/** Removes a previously attached 'change' event listener */
|
|
23
|
+
removeEventListener: (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) => void;
|
|
24
|
+
/** Manually dispatches an event */
|
|
25
|
+
triggerChange: (event: Event) => boolean;
|
|
26
|
+
/** Returns a JSON string representation of the state */
|
|
22
27
|
toString: () => string;
|
|
28
|
+
/** Returns the raw underlying value */
|
|
23
29
|
valueOf: () => T;
|
|
30
|
+
/** Handles implicit type conversion (string/number) */
|
|
24
31
|
[Symbol.toPrimitive]: (hint: string) => Primitive;
|
|
32
|
+
/** Access to the raw state data */
|
|
33
|
+
stator: T;
|
|
25
34
|
};
|
|
26
35
|
/**
|
|
27
|
-
* Creates a
|
|
28
|
-
*
|
|
29
|
-
*
|
|
36
|
+
* Creates a deeply reactive state object.
|
|
37
|
+
* * Features:
|
|
38
|
+
* - Deep reactivity via Proxy (works with nested objects and arrays)
|
|
39
|
+
* - EventTarget compatible (works in Browser and Node.js)
|
|
40
|
+
* - No redundant notifications on array mutations
|
|
41
|
+
* - Memory efficient using WeakMap for proxy tracking
|
|
42
|
+
* * @param initialState The initial value to wrap
|
|
43
|
+
* @returns A reactive Proxy object
|
|
30
44
|
*/
|
|
31
45
|
export declare function stator<T>(initialState: T): AugmentedState<T>;
|
|
32
46
|
export {};
|
package/dist/stator/Stator.js
CHANGED
|
@@ -1,103 +1,171 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Creates a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Creates a deeply reactive state object.
|
|
3
|
+
* * Features:
|
|
4
|
+
* - Deep reactivity via Proxy (works with nested objects and arrays)
|
|
5
|
+
* - EventTarget compatible (works in Browser and Node.js)
|
|
6
|
+
* - No redundant notifications on array mutations
|
|
7
|
+
* - Memory efficient using WeakMap for proxy tracking
|
|
8
|
+
* * @param initialState The initial value to wrap
|
|
9
|
+
* @returns A reactive Proxy object
|
|
5
10
|
*/
|
|
6
11
|
export function stator(initialState) {
|
|
7
12
|
/**
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
* Internal EventTarget polyfill to ensure cross-environment compatibility (Node/Browser)
|
|
14
|
+
*/
|
|
15
|
+
class StatorEventTarget {
|
|
16
|
+
et;
|
|
17
|
+
constructor() {
|
|
18
|
+
if (typeof window !== 'undefined' && typeof window.EventTarget !== 'undefined') {
|
|
19
|
+
this.et = new window.EventTarget();
|
|
20
|
+
}
|
|
21
|
+
else if (typeof document !== 'undefined' && typeof document.createElement === 'function') {
|
|
22
|
+
this.et = document.createElement('span');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Fallback for Node.js environments without built-in EventTarget
|
|
26
|
+
let listeners = {};
|
|
27
|
+
this.et = {
|
|
28
|
+
addEventListener(type, listener) {
|
|
29
|
+
if (!listeners[type])
|
|
30
|
+
listeners[type] = new Set();
|
|
31
|
+
listeners[type].add(listener);
|
|
32
|
+
},
|
|
33
|
+
removeEventListener(type, listener) {
|
|
34
|
+
listeners[type]?.delete(listener);
|
|
35
|
+
},
|
|
36
|
+
dispatchEvent(event) {
|
|
37
|
+
const ls = listeners[event.type];
|
|
38
|
+
if (!ls)
|
|
39
|
+
return true;
|
|
40
|
+
for (const l of Array.from(ls)) {
|
|
41
|
+
if (typeof l === 'function')
|
|
42
|
+
l.call(undefined, event);
|
|
43
|
+
else if (l && typeof l.handleEvent === 'function')
|
|
44
|
+
l.handleEvent(event);
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
addEventListener(...args) { this.et.addEventListener(...args); }
|
|
52
|
+
removeEventListener(...args) { this.et.removeEventListener(...args); }
|
|
53
|
+
triggerChange(event) { return this.et.dispatchEvent(event); }
|
|
54
|
+
}
|
|
55
|
+
const eventTarget = new StatorEventTarget();
|
|
56
|
+
const proxyCache = new WeakMap();
|
|
57
|
+
let onchange;
|
|
58
|
+
let onchangeListener;
|
|
59
|
+
/**
|
|
60
|
+
* Type guard to check for primitive values
|
|
11
61
|
*/
|
|
12
62
|
function isPrimitive(val) {
|
|
13
|
-
return (typeof val
|
|
14
|
-
typeof val === "number" ||
|
|
15
|
-
typeof val === "boolean");
|
|
63
|
+
return val === null || (typeof val !== "object" && typeof val !== "function");
|
|
16
64
|
}
|
|
17
65
|
/**
|
|
18
|
-
*
|
|
19
|
-
* @param message The error message
|
|
20
|
-
* @param error The error object
|
|
66
|
+
* Dispatches the change event to all subscribers
|
|
21
67
|
*/
|
|
22
|
-
function
|
|
23
|
-
|
|
68
|
+
function notify(newValue) {
|
|
69
|
+
eventTarget.triggerChange(new CustomEvent('change', { detail: { newValue } }));
|
|
24
70
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
71
|
+
/**
|
|
72
|
+
* Recursively creates a Proxy for nested objects or arrays
|
|
73
|
+
*/
|
|
74
|
+
function deepProxy(obj, info) {
|
|
75
|
+
if (isPrimitive(obj))
|
|
76
|
+
return obj;
|
|
77
|
+
if (proxyCache.has(obj))
|
|
78
|
+
return proxyCache.get(obj);
|
|
79
|
+
const handler = {
|
|
80
|
+
get(target, property, receiver) {
|
|
81
|
+
// Special internal property mapping
|
|
82
|
+
const reserved = {
|
|
83
|
+
onchange: info.getOnChange(),
|
|
84
|
+
addEventListener: eventTarget.addEventListener.bind(eventTarget),
|
|
85
|
+
removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
|
|
86
|
+
triggerChange: eventTarget.triggerChange.bind(eventTarget),
|
|
87
|
+
stator: target,
|
|
88
|
+
valueOf: () => target,
|
|
89
|
+
toString: () => JSON.stringify(target),
|
|
90
|
+
[Symbol.for('nodejs.util.inspect.custom')]: () => target
|
|
91
|
+
};
|
|
92
|
+
if (property in reserved)
|
|
93
|
+
return reserved[property];
|
|
49
94
|
if (property === Symbol.toPrimitive) {
|
|
50
|
-
return
|
|
51
|
-
if (hint === "number")
|
|
52
|
-
return Number(target.value);
|
|
53
|
-
if (hint === "string")
|
|
54
|
-
return String(target.value);
|
|
55
|
-
return isPrimitive(target.value)
|
|
56
|
-
? target.value
|
|
57
|
-
: String(target.value);
|
|
58
|
-
};
|
|
95
|
+
return (hint) => hint === "number" ? Number(target) : JSON.stringify(target);
|
|
59
96
|
}
|
|
60
|
-
|
|
97
|
+
let value = Reflect.get(target, property, receiver);
|
|
98
|
+
return isPrimitive(value) ? value : deepProxy(value, info);
|
|
99
|
+
},
|
|
100
|
+
set(target, property, value, receiver) {
|
|
101
|
+
const oldValue = target[property];
|
|
102
|
+
const proxiedValue = isPrimitive(value) ? value : deepProxy(value, info);
|
|
103
|
+
const result = Reflect.set(target, property, proxiedValue, receiver);
|
|
104
|
+
// Only notify if the value actually changed to prevent loops
|
|
105
|
+
if (oldValue !== proxiedValue) {
|
|
106
|
+
notify(info.rootState.value);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
},
|
|
110
|
+
deleteProperty(target, property) {
|
|
111
|
+
const result = Reflect.deleteProperty(target, property);
|
|
112
|
+
notify(info.rootState.value);
|
|
113
|
+
return result;
|
|
61
114
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
115
|
+
};
|
|
116
|
+
const proxy = new Proxy(obj, handler);
|
|
117
|
+
proxyCache.set(obj, proxy);
|
|
118
|
+
return proxy;
|
|
119
|
+
}
|
|
120
|
+
const stateWrapper = { value: undefined };
|
|
121
|
+
const sharedInfo = { getOnChange: () => onchange, rootState: stateWrapper };
|
|
122
|
+
// Initialize the state value
|
|
123
|
+
stateWrapper.value = isPrimitive(initialState) ? initialState : deepProxy(initialState, sharedInfo);
|
|
124
|
+
/**
|
|
125
|
+
* Root Proxy Handler
|
|
126
|
+
*/
|
|
127
|
+
const rootHandler = {
|
|
128
|
+
get(target, property, receiver) {
|
|
129
|
+
if (property === "value" || property === "stator")
|
|
130
|
+
return target.value;
|
|
131
|
+
if (property === "onchange")
|
|
132
|
+
return onchange;
|
|
133
|
+
if (property === "addEventListener")
|
|
134
|
+
return eventTarget.addEventListener.bind(eventTarget);
|
|
135
|
+
if (property === "removeEventListener")
|
|
136
|
+
return eventTarget.removeEventListener.bind(eventTarget);
|
|
137
|
+
// Allow direct property access on the state object (e.g., state.someProp)
|
|
138
|
+
if (!isPrimitive(target.value)) {
|
|
139
|
+
return Reflect.get(target.value, property, receiver);
|
|
65
140
|
}
|
|
141
|
+
return Reflect.get(target, property, receiver);
|
|
66
142
|
},
|
|
67
|
-
set(target, property, value
|
|
143
|
+
set(target, property, value) {
|
|
144
|
+
// Logic for the onchange callback property
|
|
68
145
|
if (property === "onchange") {
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const oldValue = target.value;
|
|
77
|
-
let newValue;
|
|
78
|
-
if (property === "stator") {
|
|
79
|
-
newValue = value;
|
|
80
|
-
target.value = value;
|
|
146
|
+
if (onchangeListener)
|
|
147
|
+
eventTarget.removeEventListener("change", onchangeListener);
|
|
148
|
+
if (typeof value === "function") {
|
|
149
|
+
onchange = value;
|
|
150
|
+
onchangeListener = (e) => onchange?.(e.detail.newValue);
|
|
151
|
+
eventTarget.addEventListener("change", onchangeListener);
|
|
81
152
|
}
|
|
82
|
-
else if (
|
|
83
|
-
|
|
84
|
-
newValue = { ...target.value };
|
|
153
|
+
else if (value == null) {
|
|
154
|
+
onchange = undefined;
|
|
85
155
|
}
|
|
86
156
|
else {
|
|
87
|
-
throw new
|
|
88
|
-
}
|
|
89
|
-
if (onchange && !Object.is(oldValue, newValue)) {
|
|
90
|
-
onchange(oldValue, newValue);
|
|
157
|
+
throw new TypeError("onchange must be a function or undefined");
|
|
91
158
|
}
|
|
92
159
|
return true;
|
|
93
160
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
161
|
+
// Logic for updating the core value
|
|
162
|
+
if (property === "value" || property === "stator") {
|
|
163
|
+
target.value = isPrimitive(value) ? value : deepProxy(value, sharedInfo);
|
|
164
|
+
notify(target.value);
|
|
165
|
+
return true;
|
|
97
166
|
}
|
|
98
|
-
|
|
99
|
-
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
100
169
|
};
|
|
101
|
-
|
|
102
|
-
return new Proxy(state, handler);
|
|
170
|
+
return new Proxy(stateWrapper, rootHandler);
|
|
103
171
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medyll/idae-stator",
|
|
3
|
-
"
|
|
3
|
+
"bin": {
|
|
4
|
+
"idae-stator": "./cli.js"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.51.2",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/medyll/idae.git"
|
|
10
|
+
},
|
|
4
11
|
"description": "A lightweight and efficient state management library designed for building reactive and scalable JavaScript applications.",
|
|
5
12
|
"exports": {
|
|
6
13
|
".": {
|
|
@@ -16,7 +23,12 @@
|
|
|
16
23
|
"peerDependencies": {
|
|
17
24
|
"svelte": "^5.0.0-next"
|
|
18
25
|
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"directory": "."
|
|
29
|
+
},
|
|
19
30
|
"devDependencies": {
|
|
31
|
+
"@semantic-release/github": "^10.3.5",
|
|
20
32
|
"@sveltejs/adapter-auto": "^5.0.0",
|
|
21
33
|
"@sveltejs/kit": "^2.20.2",
|
|
22
34
|
"@sveltejs/package": "^2.3.10",
|
|
@@ -35,7 +47,7 @@
|
|
|
35
47
|
"typescript-eslint": "^8.28.0",
|
|
36
48
|
"vite": "^6.2.3",
|
|
37
49
|
"vitest": "^3.0.9",
|
|
38
|
-
"@medyll/idae-
|
|
50
|
+
"@medyll/idae-eslint-config": "0.1.5"
|
|
39
51
|
},
|
|
40
52
|
"keywords": [
|
|
41
53
|
"state-management",
|
|
@@ -46,14 +58,6 @@
|
|
|
46
58
|
],
|
|
47
59
|
"author": "Your Name <your.email@example.com>",
|
|
48
60
|
"license": "MIT",
|
|
49
|
-
"repository": {
|
|
50
|
-
"type": "git",
|
|
51
|
-
"url": "https://github.com/your-repo/idae-stator.git"
|
|
52
|
-
},
|
|
53
|
-
"bugs": {
|
|
54
|
-
"url": "https://github.com/your-repo/idae-stator/issues"
|
|
55
|
-
},
|
|
56
|
-
"homepage": "https://github.com/your-repo/idae-stator#readme",
|
|
57
61
|
"svelte": "./dist/index.js",
|
|
58
62
|
"types": "./dist/index.d.ts",
|
|
59
63
|
"type": "module",
|
|
@@ -69,6 +73,6 @@
|
|
|
69
73
|
"test": "vitest",
|
|
70
74
|
"lint": "prettier --check . && eslint .",
|
|
71
75
|
"format": "prettier --write .",
|
|
72
|
-
"
|
|
76
|
+
"prepackage": "node scripts/package-pre.js"
|
|
73
77
|
}
|
|
74
78
|
}
|