🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@semantq/state

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@semantq/state - npm Package Compare versions

Comparing version
1.0.5
to
1.0.6
+6
core/props.js
// state/props.js
export function $props() {
// Your robust $props implementation goes here
console.log("Real $props logic would go here!");
return {}; // Return something meaningful
}
//bind.js
import { $effect } from './effect.js';
function bindNonReactive(element, value, options = {}) {
const formattedValue = options.format ? options.format(value) : value;
if ('value' in element || element.tagName === 'SELECT') {
// Handle form elements
if (element.type === 'checkbox') {
element.checked = !!formattedValue;
} else if (element.tagName === 'SELECT' && options.multiple && Array.isArray(formattedValue)) {
Array.from(element.options).forEach(option => {
option.selected = formattedValue.includes(option.value);
});
} else {
element.value = formattedValue ?? '';
}
} else {
// Handle non-form elements (e.g., <p>, <span>)
element.textContent = formattedValue ?? '';
}
}
export function bind(inputSelectorOrElement, stateOrValue, options = {}) {
const elements = getElements(inputSelectorOrElement);
if (!elements || elements.length === 0) {
console.warn(`Element(s) not found for binding: ${inputSelectorOrElement}`);
return;
}
const first = elements[0];
const isReactive = typeof stateOrValue === 'object' && stateOrValue !== null && 'value' in stateOrValue;
// Handle radio buttons
if (first.type === 'radio') {
return bindRadioGroup(elements, stateOrValue, options, isReactive);
}
// Handle non-reactive case
if (!isReactive) {
console.log("non reactive context", first, stateOrValue, options)
bindNonReactive(first, stateOrValue, options);
return () => {}; // Return a no-op cleanup function
}
// Handle reactive case
return bindStandardElement(first, stateOrValue, options, isReactive);
}
function bindStandardElement(element, stateOrValue, options, isReactive) {
const updateElement = () => {
let value = isReactive ? stateOrValue.value : stateOrValue;
// Format the value if a formatter is provided
if (options.format) {
value = options.format(value);
}
if (element.type === 'checkbox') {
element.checked = !!value;
} else if (element.tagName === 'SELECT') {
if (options.multiple && Array.isArray(value)) {
Array.from(element.options).forEach(option => {
option.selected = value.includes(option.value);
});
} else {
element.value = value ?? '';
}
} else {
element.value = value ?? '';
}
};
// Only set up two-way binding if it's reactive
const updateState = isReactive ? () => {
let newValue;
if (element.type === 'checkbox') {
newValue = element.checked;
} else if (element.tagName === 'SELECT' && options.multiple || element.multiple) {
newValue = Array.from(element.selectedOptions).map(o => o.value);
} else {
newValue = element.value;
}
if (options.parse) {
newValue = options.parse(newValue);
}
stateOrValue.value = newValue;
} : null;
const eventType = getEventType(element);
if (updateState) {
element.addEventListener(eventType, updateState);
}
updateElement();
// Only set up effect if it's reactive
const cleanup = isReactive ? $effect(updateElement) : null;
// Return a cleanup function for both reactive and non-reactive states
return () => {
if (updateState) {
element.removeEventListener(eventType, updateState);
}
if (cleanup) {
cleanup();
}
};
}
function bindRadioGroup(radios, stateOrValue, options, isReactive) {
const updateRadios = () => {
const value = isReactive ? stateOrValue.value : stateOrValue;
radios.forEach(r => {
r.checked = r.value === value;
});
};
// Only set up two-way binding if it's reactive
const updateState = isReactive ? (event) => {
const selected = event.target;
if (selected.checked) {
const newValue = options.parse ? options.parse(selected.value) : selected.value;
stateOrValue.value = newValue;
}
} : null;
if (updateState) {
radios.forEach(r => r.addEventListener('change', updateState));
}
updateRadios();
// Only set up effect if it's reactive
const cleanup = isReactive ? $effect(updateRadios) : null;
return () => {
if (updateState) {
radios.forEach(r => r.removeEventListener('change', updateState));
}
if (cleanup) {
cleanup();
}
};
}
function getElements(selectorOrElement) {
if (typeof selectorOrElement === 'string') {
const found = document.querySelectorAll(selectorOrElement);
return found.length ? Array.from(found) : null;
}
return [selectorOrElement];
}
function getEventType(element) {
if (element.type === 'checkbox' || element.tagName === 'SELECT') {
return 'change';
}
if (element.tagName === 'INPUT' && (element.type === 'range' || element.type === 'file')) {
return 'change';
}
return 'input';
}
// Bind textContent to state
export function bindText(selectorOrElement, stateOrValue, options = {}) {
const element = typeof selectorOrElement === 'string'
? document.querySelector(selectorOrElement)
: selectorOrElement;
if (!element) {
console.warn(`Element not found for text binding: ${selectorOrElement}`);
return;
}
const isReactive = typeof stateOrValue === 'object' && stateOrValue !== null && 'value' in stateOrValue;
const updateElement = () => {
const value = isReactive ? stateOrValue.value : stateOrValue;
element.textContent = options.format ? options.format(value) : value;
};
updateElement();
if (isReactive) {
const cleanupEffect = $effect(updateElement);
return () => cleanupEffect();
} else {
// Handle non-reactive case
//console.log("non reactive context text", element, stateOrValue, options);
//bindNonReactive(element, stateOrValue, options);
return () => {}; // Return a no-op cleanup function
}
}
// Bind attribute
export function bindAttr(selectorOrElement, attr, state, options = {}) {
const el = typeof selectorOrElement === 'string'
? document.querySelector(selectorOrElement)
: selectorOrElement;
if (!el) {
console.warn(`Element not found for attribute binding: ${selectorOrElement}`);
return;
}
// Check if the state is reactive (object with .value)
const isReactive = typeof state === 'object' && state !== null && 'value' in state;
const update = () => {
// Get value depending on whether it's reactive or not
const value = isReactive ? state.value : state;
el.setAttribute(attr, options.format ? options.format(value) : value);
};
update();
if (isReactive) {
const cleanup = $effect(update);
return () => cleanup(); // Cleanup if reactive
} else {
return () => {}; // No-op for non-reactive
}
}
// Bind class based on truthy state
export function bindClass(selectorOrElement, className, state, options = {}) {
const el = typeof selectorOrElement === 'string'
? document.querySelector(selectorOrElement)
: selectorOrElement;
if (!el) {
console.warn(`Element not found for class binding: ${selectorOrElement}`);
return;
}
// Check if the state is reactive (object with .value)
const isReactive = typeof state === 'object' && state !== null && 'value' in state;
const update = () => {
// Get value depending on whether it's reactive or not
const value = isReactive ? state.value : state;
el.classList.toggle(className, !!value);
};
update();
if (isReactive) {
const cleanup = $effect(update);
return () => cleanup(); // Cleanup if reactive
} else {
return () => {}; // No-op for non-reactive
}
}
//effect.js
import { getCurrentEffect, setCurrentEffect } from './PulseCore.js';
export function $effect(callback) {
const effect = () => {
setCurrentEffect(effect);
callback();
setCurrentEffect(null);
};
effect();
return () => {
// Cleanup logic would go here
};
}
// index.js
import { pulse } from './pulse.js';
import { reState } from './reState.js';
import { $effect } from './effect.js';
import { bind, bindText, bindAttr, bindClass } from './bind.js';
// Export the reactivity system
// Core primitives (direct named exports)
export { pulse as $state } from './pulse.js';
export { reState as $derived } from './reState.js';
export { $effect } from './effect.js';
// Binding utilities (grouped under `state`)
export const state = {
bind,
text: bindText,
attr: bindAttr,
class: bindClass,
};
// Optional: Re-export bind utilities directly for power users
export { bind, bindText, bindAttr, bindClass };
// Export storage utilities
export const storage = {
get: (key) => {
try {
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : null;
} catch (e) {
console.warn(`Failed to parse stored value for key "${key}"`, e);
return null;
}
},
set: (key, value) => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.warn(`Failed to persist value for key "${key}"`, e);
}
},
remove: (key) => {
localStorage.removeItem(key);
}
};
//pulse
import { PulseCore } from './PulseCore.js';
export function pulse(initialValue, options = {}) {
const { key, storage = localStorage } = options;
// Load from storage if key is provided
if (key) {
try {
const stored = storage.getItem(key);
if (stored !== null) {
initialValue = JSON.parse(stored);
}
} catch (e) {
console.warn(`Failed to parse stored value for key "${key}"`, e);
}
}
// Pass options to PulseCore to enable persistence!
const signal = new PulseCore(initialValue, { key, storage });
return new Proxy(signal, {
get(target, prop) {
if (prop === 'value') return target.value;
if (prop === 'set') return (newValue) => { target.value = newValue; };
return Reflect.get(target, prop);
},
set(target, prop, value) {
if (prop === 'value') {
target.value = value;
return true;
}
return Reflect.set(target, prop, value);
}
});
}
export const $state = pulse;
// PulseCore.js
let currentEffect = null;
export class PulseCore {
constructor(value, options = {}) {
this._value = value;
this._dependents = new Set();
this._options = options;
// If persistence is enabled, set up storage listener
if (this._options.key) {
window.addEventListener('storage', this._handleStorageEvent.bind(this));
}
}
get value() {
if (currentEffect) {
this._dependents.add(currentEffect);
}
return this._value;
}
set value(newValue) {
if (this._value !== newValue) {
this._value = newValue;
// Persist to storage if key is provided
if (this._options.key) {
try {
const storage = this._options.storage || localStorage;
storage.setItem(this._options.key, JSON.stringify(newValue));
} catch (e) {
console.warn(`Failed to persist state for key "${this._options.key}"`, e);
}
}
const deps = new Set(this._dependents);
this._dependents.clear();
deps.forEach(effect => effect());
}
}
_handleStorageEvent(event) {
if (event.key === this._options.key && event.storageArea === (this._options.storage || localStorage)) {
try {
const newValue = JSON.parse(event.newValue);
if (JSON.stringify(this._value) !== event.newValue) {
this._value = newValue;
const deps = new Set(this._dependents);
this._dependents.clear();
deps.forEach(effect => effect());
}
} catch (e) {
console.warn(`Failed to parse stored value for key "${this._options.key}"`, e);
}
}
}
}
export function getCurrentEffect() {
return currentEffect;
}
export function setCurrentEffect(effect) {
currentEffect = effect;
}
# Comprehensive @semantq/state Documentation
`@semantq/state` is a Semantq JS Framework state library. The library is however framework-agnostic state management library that packs a punch in under 5KB:
### Highlights
✔ **Reactivity Made Simple** – Automatic DOM updates with zero boilerplate
✔ **Built-in Persistence** – Seamless localStorage/sessionStorage integration
✔ **Tiny Footprint** – 3KB gzipped with zero dependencies
✔ **Universal Binding** – Works with vanilla JS, Svelte, Vue, React, or any JS framework
### How It Works in 30 Seconds
```bash
npm install @semantq/state
```
### Use:
```javascript
import { $state, state } from '@semantq/state';
// 1. Create reactive state (auto-persists to localStorage)
const cart = $state([], { key: 'user-cart' });
// 2. Bind to DOM elements
state.bind('#checkout-form', cart);
// 3. Automatic UI updates everywhere
function addItem(item) {
cart.value = [...cart.value, item]; // Triggers DOM updates
}
```
**Perfect for:**
- Small to medium apps needing reactivity
- JAMstack applications
- Progressive enhancement
- Framework-agnostic libraries
## Core Features
### 1. State Management
```javascript
import { $state, $derived, $effect } from '@semantq/state';
// Reactive state
const count = $state(0);
// Computed state
const doubled = $derived(() => count.value * 2);
// Side effects
$effect(() => {
console.log(`Count is: ${count.value}`);
});
```
### 2. Storage Persistence
```javascript
// localStorage persistence
const userPrefs = $state(
{ theme: 'dark' },
{ key: 'user-preferences' }
);
// sessionStorage persistence
const authToken = $state(
null,
{ key: 'auth-token', storage: sessionStorage }
);
```
### 3. DOM Binding
#### Form Element Binding
```javascript
import { state } from '@semantq/state';
// Reactive form state
const formData = $state({
username: '',
password: '',
remember: false,
plan: 'basic',
features: ['support']
});
// Bind form elements
state.bind('#username', formData.username);
state.bind('#password', formData.password);
state.bind('#remember', formData.remember);
state.bind('#plan', formData.plan);
state.bind('[name="features"]', formData.features, { multiple: true });
```
#### Binding Types Explained
**Text Inputs:**
```javascript
const searchQuery = $state('');
state.bind('#search', searchQuery);
```
**Checkboxes:**
```javascript
const agreeToTerms = $state(false);
state.bind('#terms-checkbox', agreeToTerms);
```
**Radio Groups:**
```javascript
const paymentMethod = $state('credit');
state.bind('[name="payment"]', paymentMethod);
```
**Select Elements:**
```javascript
const country = $state('US');
// Single select
state.bind('#country-select', country);
// Multi-select
const selectedFeatures = $state([]);
state.bind('#features-select', selectedFeatures, { multiple: true });
```
**Custom Formatters/Parsers:**
```javascript
const price = $state(0);
state.bind('#price-input', price, {
format: (value) => `$${value.toFixed(2)}`,
parse: (str) => parseFloat(str.replace(/[^0-9.]/g, ''))
});
```
#### Non-Form Element Binding
**Text Content:**
```javascript
const message = $state('Hello');
state.text('#message-element', message);
```
**Attributes:**
```javascript
const isActive = $state(true);
state.attr('#tab', 'aria-selected', isActive);
```
**Classes:**
```javascript
const isDarkMode = $state(false);
state.class('#theme-toggle', 'dark-mode', isDarkMode);
```
### 4. Advanced Binding Patterns
**Dynamic Element Binding:**
```javascript
const items = $state([{ id: 1, text: 'First' }]);
$effect(() => {
items.value.forEach(item => {
state.text(`#item-${item.id}`, item.text);
});
});
```
**Form Validation:**
```javascript
const form = $state({ email: '', password: '' });
const errors = $derived(() => ({
email: !form.value.email.includes('@'),
password: form.value.password.length < 8
}));
$effect(() => {
state.class('#email-input', 'error', errors.value.email);
state.class('#password-input', 'error', errors.value.password);
});
```
**Debounced Input:**
```javascript
const searchQuery = $state('', { debounce: 300 });
state.bind('#search-input', searchQuery);
$effect(() => {
// Will only trigger after 300ms of inactivity
fetchResults(searchQuery.value);
});
```
### 5. Storage Use Cases (Expanded)
**Authentication Flow:**
```javascript
// auth.js
export const auth = $state(
{ user: null, token: null },
{ key: 'auth', storage: sessionStorage }
);
export function login(credentials) {
// API call would go here
auth.value = {
user: { id: 1, name: 'User' },
token: 'abc123'
};
}
export function logout() {
auth.value = { user: null, token: null };
}
// Auto-logout when token expires
$effect(() => {
if (auth.value.token) {
const timer = setTimeout(logout, 3600000);
return () => clearTimeout(timer);
}
});
```
**E-commerce Cart:**
```javascript
// cart.js
export const cart = $state(
{ items: [], lastUpdated: null },
{ key: 'cart', debounce: 500 }
);
export function addToCart(product, quantity = 1) {
cart.value = {
items: [
...cart.value.items.filter(item => item.id !== product.id),
{ ...product, quantity }
],
lastUpdated: new Date().toISOString()
};
}
// Persist cart for 7 days
$effect(() => {
if (cart.value.lastUpdated) {
const weekOld = new Date();
weekOld.setDate(weekOld.getDate() - 7);
if (new Date(cart.value.lastUpdated) < weekOld) {
cart.value = { items: [], lastUpdated: null };
}
}
});
```
### 6. Framework Integration
**React Example:**
```javascript
import { $state, $effect } from '@semantq/state';
import { useEffect } from 'react';
function Counter() {
const count = $state(0);
// Sync state with React
const [reactCount, setReactCount] = useState(count.value);
$effect(() => setReactCount(count.value));
return (
<div>
<button onClick={() => count.value--}>-</button>
<span>{reactCount}</span>
<button onClick={() => count.value++}>+</button>
</div>
);
}
```
**Vue Example:**
```javascript
import { $state } from '@semantq/state';
export default {
setup() {
const count = $state(0);
return { count };
},
template: `
<div>
<button @click="count.value--">-</button>
{{ count.value }}
<button @click="count.value++">+</button>
</div>
`
};
```
### 7. Performance Optimization
**Batch Updates:**
```javascript
const user = $state({ name: '', email: '' });
// Instead of:
user.value.name = 'John';
user.value.email = 'john@example.com';
// Do:
user.value = { ...user.value, name: 'John', email: 'john@example.com' };
```
**Selective Binding:**
```javascript
const largeData = $state(/* ... */);
// Bind only needed properties
state.bind('#name-display', $derived(() => largeData.value.user.name));
```
### 8. Security Best Practices
**Sensitive Data Handling:**
```javascript
// auth.js
export const auth = $state(
{
// Only store minimal necessary auth data
userId: '123',
token: null, // JWT token (short-lived)
refreshToken: null // Only in memory
},
{
key: 'auth',
storage: sessionStorage,
// Optional encryption for extra security
transform: {
serialize: (value) => encrypt(JSON.stringify(value)),
deserialize: (str) => JSON.parse(decrypt(str))
}
}
);
```
### 9. Migration Strategies
**Versioned State:**
```javascript
const APP_VERSION = '1.0';
export const settings = $state(
{ version: APP_VERSION, theme: 'light' },
{
key: 'settings',
migrate: (oldValue) => {
if (!oldValue.version || oldValue.version !== APP_VERSION) {
// Return default/migrated state
return { version: APP_VERSION, theme: 'light' };
}
return oldValue;
}
}
);
```
### 10. Testing Patterns
**Mocking Storage:**
```javascript
// test-utils.js
export function createMockStorage() {
let store = {};
return {
getItem: (key) => store[key],
setItem: (key, value) => { store[key] = value; },
removeItem: (key) => { delete store[key]; },
clear: () => { store = {}; }
};
}
// In your tests:
const mockStorage = createMockStorage();
const testState = $state(0, { key: 'test', storage: mockStorage });
```
## Complete API Reference
### Core API
| Function | Description |
|----------------|-----------------------------------------------------------------------------|
| `$state(value, options?)` | Creates a reactive state container |
| `$derived(fn)` | Creates a computed value derived from other states |
| `$effect(fn)` | Runs side effects when dependencies change |
### Binding API
| Method | Description |
|---------------------------------|-----------------------------------------------------------------------------|
| `state.bind(selector, state, options?)` | Two-way binding for form elements |
| `state.text(selector, state)` | One-way binding for text content |
| `state.attr(selector, attr, state)` | One-way binding for attributes |
| `state.class(selector, className, state)` | Toggles classes based on boolean state |
### Storage API
| Method | Description |
|---------------------------|-----------------------------------------------------------------------------|
| `storage.get(key)` | Retrieves a value from storage |
| `storage.set(key, value)` | Stores a value in storage |
| `storage.remove(key)` | Removes a value from storage |
## Troubleshooting Guide
**Binding Not Working:**
1. Verify element exists when binding is called
2. Check for console errors
3. Ensure state is reactive (`$state` or `$derived`)
**Storage Issues:**
1. Check browser storage limits
2. Verify storage is not disabled (private mode)
3. Ensure data is JSON-serializable
**Performance Problems:**
1. Add debounce to frequent updates
2. Batch multiple state updates
3. Use derived state for computations
## Migration from Other Libraries
**Redux/Pinia:**
- Replace slices with individual states
- Use derived state instead of selectors
- Effects replace middleware
**MobX:**
- `$state` replaces `observable`
- `$derived` replaces `computed`
- `$effect` replaces `autorun`
## Final Recommendations
1. **Start Simple**: Begin with basic state, add persistence as needed
2. **Organize by Feature**: Group related states together
3. **Monitor Storage**: Regularly check what you're persisting
4. **Test Thoroughly**: Especially edge cases around persistence
This comprehensive approach to state management with @semantq/state provides a flexible, powerful solution that works across any JavaScript framework while maintaining excellent performance and security characteristics.
// reState.js
import { PulseCore, getCurrentEffect, setCurrentEffect } from './PulseCore.js';
export function reState(computation) {
const effect = () => {
setCurrentEffect(effect);
const newValue = computation();
setCurrentEffect(null);
if (result._value !== newValue) {
result._value = newValue;
const deps = new Set(result._dependents);
result._dependents.clear();
deps.forEach(dep => dep());
}
};
const result = new PulseCore(undefined); // Changed to PulseCore
effect();
return new Proxy(result, {
get(target, prop) {
if (prop === 'value') return target.value;
return Reflect.get(target, prop);
}
});
}
export const $derived = reState;
+1
-0

@@ -0,1 +1,2 @@

//bind.js
import { $effect } from './effect.js';

@@ -2,0 +3,0 @@

@@ -6,3 +6,5 @@ // index.js

import { bind, bindText, bindAttr, bindClass } from './bind.js';
import { $props } from './props.js';
// Export the reactivity system

@@ -13,2 +15,3 @@ // Core primitives (direct named exports)

export { $effect } from './effect.js';
export { $props };

@@ -15,0 +18,0 @@ // Binding utilities (grouped under `state`)

@@ -0,1 +1,2 @@

//pulse
import { PulseCore } from './PulseCore.js';

@@ -2,0 +3,0 @@

+33
-11

@@ -7,7 +7,9 @@ // PulseCore.js

this._value = value;
// Keep _dependents as a Set of functions (the effects)
this._dependents = new Set();
this._options = options;
// If persistence is enabled, set up storage listener
if (this._options.key) {
this._loadFromStorage(); // Load initial value if persistence enabled
window.addEventListener('storage', this._handleStorageEvent.bind(this));

@@ -17,4 +19,20 @@ }

_loadFromStorage() {
if (this._options.key) {
try {
const storage = this._options.storage || localStorage;
const stored = storage.getItem(this._options.key);
if (stored !== null) {
this._value = JSON.parse(stored);
}
} catch (e) {
console.warn(`Failed to load state for key "${this._options.key}" from storage`, e);
}
}
}
get value() {
if (currentEffect) {
// Add the current effect to this PulseCore's dependents
// The effect is responsible for clearing its *own* old dependencies
this._dependents.add(currentEffect);

@@ -26,5 +44,6 @@ }

set value(newValue) {
// Only proceed if the value has actually changed
if (this._value !== newValue) {
this._value = newValue;
// Persist to storage if key is provided

@@ -39,9 +58,11 @@ if (this._options.key) {

}
const deps = new Set(this._dependents);
this._dependents.clear();
deps.forEach(effect => effect());
// Notify all subscribed effects to re-run
// Create a new array from the Set to prevent issues if effects modify the Set during iteration
[...this._dependents].forEach(effect => effect());
// IMPORTANT: Do NOT clear this._dependents here.
// Dependents will re-add themselves when their effect re-runs and reads this value.
}
}
_handleStorageEvent(event) {

@@ -51,7 +72,8 @@ if (event.key === this._options.key && event.storageArea === (this._options.storage || localStorage)) {

const newValue = JSON.parse(event.newValue);
if (JSON.stringify(this._value) !== event.newValue) {
// Only update if the value from storage is different to avoid unnecessary notifications
if (JSON.stringify(this._value) !== event.newValue) { // Compare stringified to handle complex objects
this._value = newValue;
const deps = new Set(this._dependents);
this._dependents.clear();
deps.forEach(effect => effect());
// Notify internal dependents (effects) that this value has changed
// This is important for cross-tab or cross-window sync
[...this._dependents].forEach(effect => effect());
}

@@ -58,0 +80,0 @@ } catch (e) {

// reState.js
import { PulseCore, getCurrentEffect, setCurrentEffect } from './PulseCore.js';
// No need to import getCurrentEffect, setCurrentEffect here directly,
// as $effect (from effect.js) will handle that.
import { PulseCore } from './PulseCore.js';
import { $effect } from './effect.js'; // Ensure this path is correct relative to reState.js
/**
* Creates a reactive derived value.
* Its value is computed from other reactive states ($state) or other derived values ($derived).
* It automatically recomputes when its dependencies change and notifies its own subscribers.
* @param {Function} computation A function that computes the derived value. This function
* should read from other reactive states/deriveds.
* @returns {PulseCore} A PulseCore instance that holds the derived value.
*/
export function reState(computation) {
const effect = () => {
setCurrentEffect(effect);
// 1. Create the PulseCore instance to hold the derived value.
// This `derivedPulse` instance will also manage its own dependents ($effect blocks
// that read this derived value).
const derivedPulse = new PulseCore(undefined); // Initialize with undefined, its value will be set by computation()
// 2. Wrap the `computation` function inside an $effect.
// This internal $effect will automatically track the dependencies (other $state or $derived
// values) that are accessed when `computation` is executed.
$effect(() => {
// When this internal effect runs, it recomputes the derived value.
const newValue = computation();
setCurrentEffect(null);
if (result._value !== newValue) {
result._value = newValue;
const deps = new Set(result._dependents);
result._dependents.clear();
deps.forEach(dep => dep());
// 3. Update the value of the `derivedPulse` ONLY IF it has changed.
// Importantly, setting `derivedPulse.value` will trigger the setter logic
// in `PulseCore`, which then notifies *its own* subscribers (the $effect blocks
// that depend on this derived value, like your if_condition_X variables).
// This is crucial for propagating the change.
if (derivedPulse.value !== newValue) {
derivedPulse.value = newValue;
}
};
const result = new PulseCore(undefined); // Changed from Signal to PulseCore
effect();
return new Proxy(result, {
get(target, prop) {
if (prop === 'value') return target.value;
return Reflect.get(target, prop);
}
});
// Return the PulseCore instance directly.
// It already has a `value` getter/setter, so a Proxy is not needed for basic access.
return derivedPulse;
}
// Export as $derived for use in your transpiled code
export const $derived = reState;
{
"name": "@semantq/state",
"version": "1.0.5",
"description": "A reactive state management system for Semantq",
"version": "1.0.6",
"description": "A reactivity state management system for Semantq",
"main": "./core/index.js",

@@ -6,0 +6,0 @@ "type": "module",