@yiin/reactive-proxy-state 1.0.4 → 1.0.5

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.
@@ -6,11 +6,16 @@ export interface ComputedRef<T = any> extends Omit<Ref<T>, 'value'> {
6
6
  readonly [isRefSymbol]: true;
7
7
  }
8
8
  export interface WritableComputedRef<T> extends Ref<T> {
9
+ readonly [isComputedSymbol]: true;
10
+ readonly [isRefSymbol]: true;
9
11
  }
10
12
  type ComputedGetter<T> = () => T;
13
+ type ComputedSetter<T> = (v: T) => void;
14
+ interface WritableComputedOptions<T> {
15
+ get: ComputedGetter<T>;
16
+ set: ComputedSetter<T>;
17
+ }
11
18
  export declare function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>;
12
- /**
13
- * Checks if a value is a computed ref.
14
- */
15
- export declare function isComputed<T>(c: any): c is ComputedRef<T>;
19
+ export declare function computed<T>(options: WritableComputedOptions<T>): WritableComputedRef<T>;
20
+ export declare function isComputed<T>(c: any): c is ComputedRef<T> | WritableComputedRef<T>;
16
21
  export {};
package/dist/computed.js CHANGED
@@ -1,59 +1,58 @@
1
1
  import { watchEffect, track, trigger } from './watchEffect';
2
2
  import { isRefSymbol } from './ref';
3
- // Symbol for marking computed refs
3
+ // symbol for identifying computed refs
4
4
  const isComputedSymbol = Symbol('isComputed');
5
- // Implementation v4 - Using watchEffect with scheduler
6
- export function computed(getter) {
5
+ // implementation using a lazy watchEffect with a custom scheduler for caching
6
+ export function computed(getterOrOptions) {
7
+ let getter;
8
+ let setter;
9
+ const isGetter = typeof getterOrOptions === 'function';
10
+ if (isGetter) {
11
+ getter = getterOrOptions;
12
+ }
13
+ else {
14
+ getter = getterOrOptions.get;
15
+ setter = getterOrOptions.set;
16
+ }
7
17
  let _value;
8
- let _dirty = true; // Start dirty
9
- let computedRef; // Placeholder
10
- // Create a lazy effect with a scheduler
18
+ let _dirty = true; // flag to track if the cached value is stale
19
+ let computedRef; // placeholder to allow self-reference in scheduler
20
+ // create a lazy effect; scheduler intercepts triggers to mark dirty instead of recomputing immediately
11
21
  const stopHandle = watchEffect(getter, {
12
- lazy: true, // Don't run the getter immediately
22
+ lazy: true,
13
23
  scheduler: () => {
14
- // When a dependency changes, don't re-run the getter immediately.
15
- // Instead, mark the computed as dirty and trigger downstream effects.
16
24
  if (!_dirty) {
17
25
  _dirty = true;
18
- // Trigger effects that depend on the computed ref's value
26
+ // trigger effects that depend on this computed ref
19
27
  trigger(computedRef, 'value');
20
28
  }
21
29
  },
22
30
  });
23
- // Access the internal effect runner
24
31
  const effectRunner = stopHandle.effect;
25
32
  computedRef = {
26
33
  [isRefSymbol]: true,
27
34
  [isComputedSymbol]: true,
28
35
  get value() {
29
- // 1. Track access to this computed value for any outer effects
30
36
  track(computedRef, 'value');
31
- // 2. If dirty, run the effect manually. This will:
32
- // - Execute the getter
33
- // - Update _value (via getter's return)
34
- // - Track dependencies for the getter (handled by watchEffect internals)
35
- // - Set _dirty to false
37
+ // if dirty, recompute value by running the getter
36
38
  if (_dirty) {
37
- // console.log('Recomputing computed value via getter access');
38
- _value = effectRunner.run(); // Run the getter, update value, track deps
39
- _dirty = false; // Mark as clean *after* successful run
39
+ _value = effectRunner.run();
40
+ _dirty = false; // mark as clean after successful run
40
41
  }
41
- // 3. Return the (now current) value.
42
42
  return _value;
43
43
  },
44
44
  set value(newValue) {
45
- console.warn('Computed value is read-only');
45
+ if (setter) {
46
+ setter(newValue);
47
+ }
48
+ else {
49
+ console.warn('computed value is read-only');
50
+ }
46
51
  },
47
- // Expose the stop function if needed
48
- // stop: stopHandle
52
+ // stop: stopHandle // potentially expose stop handle
49
53
  };
50
- // Initial computation is lazy, happens on first .value access.
51
- // The scheduler ensures dependency changes only mark it dirty.
52
54
  return computedRef;
53
55
  }
54
- /**
55
- * Checks if a value is a computed ref.
56
- */
57
56
  export function isComputed(c) {
58
57
  return !!(c && c[isComputedSymbol]);
59
58
  }
package/dist/reactive.js CHANGED
@@ -3,18 +3,22 @@ import { wrapArray } from './wrapArray';
3
3
  import { wrapMap } from './wrapMap';
4
4
  import { wrapSet } from './wrapSet';
5
5
  import { track, trigger } from './watchEffect';
6
- // Pre-allocate type check function
6
+ // avoid repeated typeof checks
7
7
  function isObject(v) {
8
8
  return v && typeof v === 'object';
9
9
  }
10
+ // create a reactive proxy for an object
10
11
  export function reactive(obj, emit, path = [], seen = globalSeen) {
12
+ // prevent infinite recursion with circular references
11
13
  if (seen.has(obj))
12
14
  return seen.get(obj);
15
+ // helper to wrap nested values recursively
13
16
  function wrapValue(val, subPath) {
14
17
  if (!isObject(val))
15
- return val;
18
+ return val; // primitives are returned directly
16
19
  if (seen.has(val))
17
- return seen.get(val);
20
+ return seen.get(val); // handle cycles within nested structures
21
+ // delegate wrapping to specific functions based on type
18
22
  if (Array.isArray(val))
19
23
  return wrapArray(val, emit, subPath);
20
24
  if (val instanceof Map)
@@ -22,43 +26,48 @@ export function reactive(obj, emit, path = [], seen = globalSeen) {
22
26
  if (val instanceof Set)
23
27
  return wrapSet(val, emit, subPath);
24
28
  if (val instanceof Date)
25
- return new Date(val.getTime());
29
+ return new Date(val.getTime()); // dates are not proxied, return copy
30
+ // default to reactive for plain objects
26
31
  return reactive(val, emit, subPath, seen);
27
32
  }
28
33
  const proxy = new Proxy(obj, {
29
34
  get(target, prop, receiver) {
30
35
  const value = Reflect.get(target, prop, receiver);
31
- // Track this property access for reactivity
36
+ // track property access for dependency tracking
32
37
  track(target, prop);
33
- // Fast path for non-objects
38
+ // return non-objects directly without wrapping
34
39
  if (!isObject(value))
35
40
  return value;
36
- // Use cached path concatenation
37
- const pathKey = `${path.join('.')}.${String(prop)}`;
41
+ // calculate the path for the nested property, using cache for performance
42
+ const propKey = String(prop);
43
+ const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
38
44
  let newPath = getPathConcat(pathKey);
39
45
  if (newPath === undefined) {
40
- newPath = path.concat(String(prop));
46
+ newPath = path.concat(propKey);
41
47
  setPathConcat(pathKey, newPath);
42
48
  }
49
+ // wrap the nested value if it's an object/collection
43
50
  return wrapValue(value, newPath);
44
51
  },
45
52
  set(target, prop, value, receiver) {
46
53
  const oldValue = target[prop];
47
- // Fast path for primitive equality
54
+ // avoid unnecessary triggers if the value hasn't changed
55
+ // fast path for primitives
48
56
  if (oldValue === value)
49
57
  return true;
50
- // Only do deep equality check for objects
51
- if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value))
52
- return true;
58
+ // deep equality check for objects/arrays
59
+ if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
60
+ return true; // use new WeakMap for deepEqual seen
53
61
  const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
54
62
  const result = Reflect.set(target, prop, value, receiver);
55
- // Only emit if the set was successful and it's not a setter property
63
+ // only emit and trigger if the set was successful and wasn't intercepted by a setter
56
64
  if (result && (!descriptor || !descriptor.set)) {
57
- // Use cached path concatenation
58
- const pathKey = `${path.join('.')}.${String(prop)}`;
65
+ // calculate path, using cache
66
+ const propKey = String(prop);
67
+ const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
59
68
  let newPath = getPathConcat(pathKey);
60
69
  if (newPath === undefined) {
61
- newPath = path.concat(String(prop));
70
+ newPath = path.concat(propKey);
62
71
  setPathConcat(pathKey, newPath);
63
72
  }
64
73
  const event = {
@@ -68,32 +77,38 @@ export function reactive(obj, emit, path = [], seen = globalSeen) {
68
77
  newValue: value
69
78
  };
70
79
  emit(event);
71
- // Trigger effects
80
+ // notify effects watching this property
72
81
  trigger(target, prop);
73
82
  }
74
83
  return result;
75
84
  },
76
85
  deleteProperty(target, prop) {
77
86
  const oldValue = target[prop];
87
+ const hadProperty = Object.prototype.hasOwnProperty.call(target, prop);
78
88
  const result = Reflect.deleteProperty(target, prop);
79
- // Use cached path concatenation
80
- const pathKey = `${path.join('.')}.${String(prop)}`;
81
- let newPath = getPathConcat(pathKey);
82
- if (newPath === undefined) {
83
- newPath = path.concat(String(prop));
84
- setPathConcat(pathKey, newPath);
89
+ // only emit and trigger if the property existed and deletion was successful
90
+ if (hadProperty && result) {
91
+ // calculate path, using cache
92
+ const propKey = String(prop);
93
+ const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
94
+ let newPath = getPathConcat(pathKey);
95
+ if (newPath === undefined) {
96
+ newPath = path.concat(propKey);
97
+ setPathConcat(pathKey, newPath);
98
+ }
99
+ const event = {
100
+ action: 'delete',
101
+ path: newPath,
102
+ oldValue
103
+ };
104
+ emit(event);
105
+ // notify effects watching this property
106
+ trigger(target, prop);
85
107
  }
86
- const event = {
87
- action: 'delete',
88
- path: newPath,
89
- oldValue
90
- };
91
- emit(event);
92
- // Trigger effects
93
- trigger(target, prop);
94
108
  return result;
95
109
  }
96
110
  });
111
+ // cache the proxy to handle circular references and improve performance
97
112
  seen.set(obj, proxy);
98
113
  return proxy;
99
114
  }
package/dist/ref.d.ts CHANGED
@@ -4,19 +4,20 @@ export interface Ref<T = any> {
4
4
  readonly [isRefSymbol]: true;
5
5
  }
6
6
  /**
7
- * Takes an inner value and returns a reactive and mutable ref object,
8
- * which has a single property `.value` that points to the inner value.
9
- * The ref tracks access and mutations to its `.value` property.
10
- * If the initial value is an object, it is NOT automatically made reactive.
7
+ * creates a reactive reference object.
8
+ * the object has a single `.value` property.
9
+ * reactivity is tracked on access and mutation of the `.value` property.
10
+ * if an object is passed as the initial value, the object itself is *not* made deeply reactive.
11
+ * only the assignment to `.value` is tracked.
11
12
  */
12
13
  export declare function ref<T>(value: T): Ref<T>;
13
14
  export declare function ref<T = undefined>(): Ref<T | undefined>;
14
15
  /**
15
- * Checks if a value is a ref object.
16
+ * checks if a value is a ref object.
16
17
  */
17
18
  export declare function isRef<T>(r: any): r is Ref<T>;
18
19
  /**
19
- * Returns the inner value if the argument is a ref, otherwise returns the
20
- * argument itself.
20
+ * returns the inner value if the argument is a ref,
21
+ * otherwise returns the argument itself. this is a sugar for `isRef(val) ? val.value : val`.
21
22
  */
22
23
  export declare function unref<T>(refValue: T | Ref<T>): T;
package/dist/ref.js CHANGED
@@ -1,51 +1,51 @@
1
1
  import { track, trigger } from './watchEffect';
2
2
  // Removed reactive import as ref doesn't automatically make contained objects reactive
3
3
  // import { reactive } from './reactive';
4
- // Symbol for marking refs
5
- export const isRefSymbol = Symbol('isRef'); // Add export and Simplified symbol description
4
+ // symbol used to identify refs internally and via isRef()
5
+ export const isRefSymbol = Symbol('isRef');
6
6
  export function ref(value) {
7
7
  return createRef(value);
8
8
  }
9
- // Internal function to create refs (no longer shallow distinction needed here)
9
+ // internal factory for creating ref objects
10
10
  function createRef(rawValue) {
11
- // If the value is already a ref, return it directly
11
+ // avoid wrapping if the value is already a ref
12
12
  if (isRef(rawValue)) {
13
- // Cast rawValue back to Ref<T> after type guard
14
13
  return rawValue;
15
14
  }
16
- // The ref holds the raw value directly
17
- let value = rawValue;
18
- // Create the ref object with getter/setter for reactivity
15
+ // store the inner value
16
+ let _value = rawValue;
17
+ // create the ref object with a getter/setter on `.value`
19
18
  const r = {
20
- [isRefSymbol]: true, // Mark as ref
19
+ [isRefSymbol]: true, // mark as a ref using the symbol
21
20
  get value() {
22
- // Track dependency on the 'value' property of this ref object
23
- // Ensure 'r' is treated as the target object for tracking
21
+ // track dependency when `.value` is accessed
22
+ // `r` (the ref object itself) is the target for tracking
24
23
  track(r, 'value');
25
- return value;
24
+ return _value;
26
25
  },
27
26
  set value(newValue) {
28
- // Check if value actually changed (using simple comparison)
29
- // For objects, this means identity change, not deep mutation.
30
- if (value !== newValue) {
31
- value = newValue;
32
- // Trigger effects depending on the 'value' property of this ref object
33
- // Ensure 'r' is treated as the target object for triggering
27
+ // only update and trigger if the value has actually changed
28
+ // this uses strict equality (===), so for objects, it checks reference equality
29
+ if (_value !== newValue) {
30
+ _value = newValue;
31
+ // trigger effects when `.value` is assigned a new value
32
+ // `r` (the ref object itself) is the target for triggering
34
33
  trigger(r, 'value');
35
34
  }
36
35
  },
37
- }; // Explicit cast to ensure type correctness
36
+ }; // cast to ensure the object conforms to the Ref interface
38
37
  return r;
39
38
  }
40
39
  /**
41
- * Checks if a value is a ref object.
40
+ * checks if a value is a ref object.
42
41
  */
43
42
  export function isRef(r) {
43
+ // check for the presence of the internal symbol
44
44
  return !!(r && r[isRefSymbol]);
45
45
  }
46
46
  /**
47
- * Returns the inner value if the argument is a ref, otherwise returns the
48
- * argument itself.
47
+ * returns the inner value if the argument is a ref,
48
+ * otherwise returns the argument itself. this is a sugar for `isRef(val) ? val.value : val`.
49
49
  */
50
50
  export function unref(refValue) {
51
51
  return isRef(refValue) ? refValue.value : refValue;