@yiin/reactive-proxy-state 1.0.8 → 1.0.10

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 CHANGED
@@ -24,6 +24,94 @@ bun add @yiin/reactive-proxy-state
24
24
  2. **Dependency Tracking**: When code inside a `watchEffect` reads a property of a reactive object, a dependency is established.
25
25
  3. **Effect Triggering**: When a tracked property is mutated, any dependent effects (`watchEffect` or `watch` callbacks) are re-run **synchronously**.
26
26
 
27
+ ## State Synchronization with `updateState`
28
+
29
+ A key feature is `updateState`, which allows applying changes from a plain JavaScript object (often received from serialization) onto an existing reactive state object. It intelligently updates properties, adds/removes array elements, and modifies Maps/Sets to match the target structure, triggering reactive effects only where necessary.
30
+
31
+ This is typically used with the `emit` callback of `reactive` for state synchronization:
32
+
33
+ ```typescript
34
+ import { reactive, updateState, watchEffect } from '@yiin/reactive-proxy-state';
35
+
36
+ // Assume these functions exist:
37
+ // - getInitialStateFromServer(): Fetches the initial state snapshot.
38
+ // - sendEventToClient(event): Sends a state change event to the client.
39
+ // - listenForServerEvents(callback): Sets up a listener for events from the server.
40
+
41
+ // --- Source State (e.g., Server) ---
42
+ const sourceData = {
43
+ counter: 0,
44
+ user: { name: 'Alice' },
45
+ items: ['a']
46
+ };
47
+
48
+ // 1. Server creates reactive state & emits deltas via sendEventToClient
49
+ const sourceState = reactive(sourceData, sendEventToClient);
50
+
51
+
52
+ // --- Client Initialization & Sync ---
53
+ // 2. Client creates its reactive state holder *before* data arrives
54
+ const targetState = reactive({});
55
+ console.log('Client: Initial empty target state created:', targetState);
56
+
57
+ // 3. Client watches its local state for changes (e.g., for UI updates)
58
+ watchEffect(() => {
59
+ console.log('Client: Target state updated:', targetState);
60
+ });
61
+ // Initial output: Client: Target state updated: {}
62
+
63
+ // 4. Client fetches the initial state snapshot
64
+ const initialSnapshot = getInitialStateFromServer(); // Assume fetches { counter: 0, ... }
65
+ console.log('\n--- Client Received Initial Snapshot ---');
66
+ console.log(initialSnapshot);
67
+
68
+ // 5. Client applies the initial snapshot using a 'replace' action
69
+ // (Requires updateState implementation to support action: 'replace')
70
+ updateState(targetState, {
71
+ action: 'replace',
72
+ path: [], // Apply to the root
73
+ newValue: initialSnapshot
74
+ });
75
+ // Output after 'replace':
76
+ // Client: Target state updated: { counter: 0, user: { name: 'Alice' }, items: [ 'a' ] }
77
+
78
+ // 6. Client starts listening for subsequent delta events from the server
79
+ listenForServerEvents((event) => {
80
+ console.log(`Client: Received delta event <- Server:`, event);
81
+ // Apply the delta event normally
82
+ updateState(targetState, event);
83
+ });
84
+
85
+
86
+ // --- Subsequent Server Modifications ---
87
+ // 7. Server state is modified *after* the client has initialized
88
+ console.log('\n--- Server Modifying State (Post-Init) ---');
89
+ sourceState.counter++;
90
+ // Output: (sendEventToClient called with { action: 'set', path: [ 'counter' ], ... })
91
+ sourceState.user.name = 'Charlie';
92
+ // Output: (sendEventToClient called with { action: 'set', path: [ 'user', 'name' ], ... })
93
+ sourceState.items.push('b');
94
+ // Output: (sendEventToClient called with { action: 'array-push', path: [ 'items' ], ... })
95
+
96
+ // --- Delta Events arrive and are processed asynchronously by the client ---
97
+
98
+ // Example Console Output Order (assuming async processing):
99
+ // Client: Initial empty target state created: {}
100
+ // Client: Target state updated: {}
101
+ // --- Client Received Initial Snapshot ---
102
+ // { counter: 0, user: { name: 'Alice' }, items: ['a'] }
103
+ // Client: Target state updated: { counter: 0, user: { name: 'Alice' }, items: [ 'a' ] } // From 'replace'
104
+ // --- Server Modifying State (Post-Init) ---
105
+ // Client: Received delta event <- Server: { action: 'set', path: [ 'counter' ], oldValue: 0, newValue: 1 }
106
+ // Client: Target state updated: { counter: 1, user: { name: 'Alice' }, items: [ 'a' ] }
107
+ // Client: Received delta event <- Server: { action: 'set', path: [ 'user', 'name' ], oldValue: 'Alice', newValue: 'Charlie' }
108
+ // Client: Target state updated: { counter: 1, user: { name: 'Charlie' }, items: [ 'a' ] }
109
+ // Client: Received delta event <- Server: { action: 'array-push', path: [ 'items' ], key: 1, items: [ 'b' ] }
110
+ // Client: Target state updated: { counter: 1, user: { name: 'Charlie' }, items: [ 'a', 'b' ] }
111
+ ```
112
+
113
+ See the [`updateState` documentation](./docs/api/update-state.md) and [`reactive` documentation](./docs/api/reactive.md) for more details on event emission and application.
114
+
27
115
  ## API
28
116
 
29
117
  ### `reactive<T extends object>(obj: T): T`
package/dist/computed.js CHANGED
@@ -1,4 +1,4 @@
1
- import { watchEffect, track, trigger } from './watchEffect';
1
+ import { watchEffect, track, trigger } from './watch-effect';
2
2
  import { isRefSymbol } from './ref';
3
3
  // symbol for identifying computed refs
4
4
  const isComputedSymbol = Symbol('isComputed');
package/dist/index.d.ts CHANGED
@@ -2,10 +2,10 @@ export * from './types';
2
2
  export * from './utils';
3
3
  export * from './state';
4
4
  export * from './reactive';
5
- export * from './wrapArray';
6
- export * from './wrapMap';
7
- export * from './wrapSet';
5
+ export * from './wrap-array';
6
+ export * from './wrap-map';
7
+ export * from './wrap-set';
8
8
  export * from './watch';
9
- export * from './watchEffect';
9
+ export * from './watch-effect';
10
10
  export * from './ref';
11
11
  export * from './computed';
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@ export * from './types';
2
2
  export * from './utils';
3
3
  export * from './state';
4
4
  export * from './reactive';
5
- export * from './wrapArray';
6
- export * from './wrapMap';
7
- export * from './wrapSet';
5
+ export * from './wrap-array';
6
+ export * from './wrap-map';
7
+ export * from './wrap-set';
8
8
  export * from './watch';
9
- export * from './watchEffect';
9
+ export * from './watch-effect';
10
10
  export * from './ref';
11
11
  export * from './computed';
package/dist/reactive.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { deepEqual, globalSeen, getPathConcat, setPathConcat } from './utils';
2
- import { wrapArray } from './wrapArray';
3
- import { wrapMap } from './wrapMap';
4
- import { wrapSet } from './wrapSet';
5
- import { track, trigger } from './watchEffect';
2
+ import { wrapArray } from './wrap-array';
3
+ import { wrapMap } from './wrap-map';
4
+ import { wrapSet } from './wrap-set';
5
+ import { track, trigger } from './watch-effect';
6
6
  // avoid repeated typeof checks
7
7
  function isObject(v) {
8
8
  return v && typeof v === 'object';
package/dist/ref.d.ts CHANGED
@@ -21,3 +21,19 @@ export declare function isRef<T>(r: any): r is Ref<T>;
21
21
  * otherwise returns the argument itself. this is a sugar for `isRef(val) ? val.value : val`.
22
22
  */
23
23
  export declare function unref<T>(refValue: T | Ref<T>): T;
24
+ /**
25
+ * Converts an object's properties to reactive refs.
26
+ * This is useful when you want to destructure reactive objects but maintain reactivity.
27
+ * @param object The reactive object to convert to refs
28
+ * @returns An object with the same properties, where each property is a ref connected to the original object
29
+ */
30
+ export declare function toRefs<T extends object>(object: T): {
31
+ [K in keyof T]: Ref<T[K]>;
32
+ };
33
+ /**
34
+ * Creates a ref that is connected to a property on an object.
35
+ * @param object The source object
36
+ * @param key The property key
37
+ * @returns A ref connected to the object's property
38
+ */
39
+ export declare function toRef<T extends object, K extends keyof T>(object: T, key: K): Ref<T[K]>;
package/dist/ref.js CHANGED
@@ -1,4 +1,4 @@
1
- import { track, trigger } from './watchEffect';
1
+ import { track, trigger } from './watch-effect';
2
2
  // Removed reactive import as ref doesn't automatically make contained objects reactive
3
3
  // import { reactive } from './reactive';
4
4
  // symbol used to identify refs internally and via isRef()
@@ -50,6 +50,37 @@ export function isRef(r) {
50
50
  export function unref(refValue) {
51
51
  return isRef(refValue) ? refValue.value : refValue;
52
52
  }
53
+ /**
54
+ * Converts an object's properties to reactive refs.
55
+ * This is useful when you want to destructure reactive objects but maintain reactivity.
56
+ * @param object The reactive object to convert to refs
57
+ * @returns An object with the same properties, where each property is a ref connected to the original object
58
+ */
59
+ export function toRefs(object) {
60
+ const result = {};
61
+ for (const key in object) {
62
+ result[key] = toRef(object, key);
63
+ }
64
+ return result;
65
+ }
66
+ /**
67
+ * Creates a ref that is connected to a property on an object.
68
+ * @param object The source object
69
+ * @param key The property key
70
+ * @returns A ref connected to the object's property
71
+ */
72
+ export function toRef(object, key) {
73
+ return {
74
+ [isRefSymbol]: true,
75
+ get value() {
76
+ track(this, 'value');
77
+ return object[key];
78
+ },
79
+ set value(newVal) {
80
+ object[key] = newVal;
81
+ }
82
+ };
83
+ }
53
84
  // Basic triggerRef function (may need refinement if used)
54
85
  /*
55
86
  export function triggerRef(ref: Ref<any>): void {
@@ -0,0 +1,54 @@
1
+ type EffectCallback<T = any> = () => T;
2
+ type Scheduler = (job: () => void) => void;
3
+ export interface WatchEffectStopHandle<T = any> {
4
+ (): void;
5
+ effect: TrackedEffect<T>;
6
+ }
7
+ export interface TrackedEffect<T = any> {
8
+ run: () => T;
9
+ dependencies?: Set<Set<TrackedEffect<any>>>;
10
+ options?: WatchEffectOptions;
11
+ active?: boolean;
12
+ _rawCallback: EffectCallback<T>;
13
+ }
14
+ export declare let activeEffect: TrackedEffect<any> | null;
15
+ export declare function setActiveEffect(effect: TrackedEffect<any> | null): void;
16
+ /**
17
+ * removes an effect from all dependency sets it belongs to.
18
+ * this is crucial to prevent memory leaks and unnecessary updates when an effect is stopped or re-run.
19
+ */
20
+ export declare function cleanupEffect(effect: TrackedEffect<any>): void;
21
+ /**
22
+ * establishes a dependency between the currently active effect and a specific object property.
23
+ * called by proxy getters or ref getters.
24
+ */
25
+ export declare function track(target: object, key: string | symbol): void;
26
+ /**
27
+ * triggers all active effects associated with a specific object property.
28
+ * called by proxy setters/deleters or ref setters.
29
+ * currently runs effects synchronously.
30
+ */
31
+ export declare function trigger(target: object, key: string | symbol): void;
32
+ export interface WatchEffectOptions {
33
+ lazy?: boolean;
34
+ scheduler?: Scheduler;
35
+ onTrack?: (event: {
36
+ effect: EffectCallback<any>;
37
+ target: object;
38
+ key: string | symbol;
39
+ type: 'track';
40
+ }) => void;
41
+ onTrigger?: (event: {
42
+ effect: EffectCallback<any>;
43
+ target: object;
44
+ key: string | symbol;
45
+ type: 'trigger';
46
+ }) => void;
47
+ }
48
+ /**
49
+ * runs a function immediately, tracks its reactive dependencies, and re-runs it
50
+ * synchronously whenever any of those dependencies change.
51
+ * returns a stop handle to manually stop the effect.
52
+ */
53
+ export declare function watchEffect<T>(effectCallback: EffectCallback<T>, options?: WatchEffectOptions): WatchEffectStopHandle<T>;
54
+ export {};
@@ -0,0 +1,154 @@
1
+ // tracks the currently executing effect to establish dependencies
2
+ export let activeEffect = null;
3
+ // allows setting the active effect, used internally by the effect runner
4
+ export function setActiveEffect(effect) {
5
+ activeEffect = effect;
6
+ }
7
+ // storage for dependencies: target object -> property key -> set of effects that depend on this key
8
+ const targetMap = new WeakMap();
9
+ /**
10
+ * removes an effect from all dependency sets it belongs to.
11
+ * this is crucial to prevent memory leaks and unnecessary updates when an effect is stopped or re-run.
12
+ */
13
+ export function cleanupEffect(effect) {
14
+ if (effect.dependencies) {
15
+ effect.dependencies.forEach(dep => {
16
+ // remove this effect from the dependency set associated with a specific target/key
17
+ dep.delete(effect);
18
+ });
19
+ // clear the effect's own list of dependencies for the next run
20
+ effect.dependencies.clear();
21
+ }
22
+ }
23
+ /**
24
+ * establishes a dependency between the currently active effect and a specific object property.
25
+ * called by proxy getters or ref getters.
26
+ */
27
+ export function track(target, key) {
28
+ // do nothing if there is no active effect or if the effect is stopped
29
+ if (!activeEffect || !activeEffect.active)
30
+ return;
31
+ // get or create the dependency map for the target object
32
+ let depsMap = targetMap.get(target);
33
+ if (!depsMap) {
34
+ depsMap = new Map();
35
+ targetMap.set(target, depsMap);
36
+ }
37
+ // get or create the set of effects for the specific property key
38
+ let dep = depsMap.get(key);
39
+ if (!dep) {
40
+ dep = new Set();
41
+ depsMap.set(key, dep);
42
+ }
43
+ // add the current effect to the dependency set if it's not already there
44
+ const effectToAdd = activeEffect;
45
+ if (!dep.has(effectToAdd)) {
46
+ dep.add(effectToAdd);
47
+ // also add this dependency set to the effect's own tracking list for cleanup purposes
48
+ if (!effectToAdd.dependencies) {
49
+ effectToAdd.dependencies = new Set();
50
+ }
51
+ effectToAdd.dependencies.add(dep);
52
+ // trigger the onTrack debug hook if provided
53
+ if (effectToAdd.options?.onTrack) {
54
+ // pass the original user callback to the hook, not the internal wrapper
55
+ effectToAdd.options.onTrack({ effect: effectToAdd._rawCallback, target, key, type: 'track' });
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * triggers all active effects associated with a specific object property.
61
+ * called by proxy setters/deleters or ref setters.
62
+ * currently runs effects synchronously.
63
+ */
64
+ export function trigger(target, key) {
65
+ const depsMap = targetMap.get(target);
66
+ if (!depsMap)
67
+ return; // no effects tracked for this target
68
+ // use a set to collect effects to run, avoiding duplicate executions within the same trigger cycle
69
+ const effectsToRun = new Set();
70
+ // helper to add effects from a specific dependency set to the run queue
71
+ const addEffects = (depKey) => {
72
+ const dep = depsMap.get(depKey);
73
+ if (dep) {
74
+ dep.forEach(effect => {
75
+ // avoid triggering the effect if it's the one currently running (prevents infinite loops)
76
+ // also ensure the effect hasn't been stopped
77
+ if (effect !== activeEffect && effect.active) {
78
+ effectsToRun.add(effect);
79
+ }
80
+ });
81
+ }
82
+ };
83
+ // add effects associated with the specific key that changed
84
+ addEffects(key);
85
+ // todo: consider adding effects associated with iteration keys (like Symbol.iterator or 'length' for arrays) if applicable
86
+ // schedule or run the collected effects
87
+ effectsToRun.forEach(effect => {
88
+ // trigger the onTrigger debug hook if provided
89
+ if (effect.options?.onTrigger) {
90
+ effect.options.onTrigger({ effect: effect._rawCallback, target, key, type: 'trigger' });
91
+ }
92
+ // use a custom scheduler if provided, otherwise run the effect synchronously
93
+ if (effect.options?.scheduler) {
94
+ effect.options.scheduler(effect.run);
95
+ }
96
+ else {
97
+ effect.run(); // execute the effect's wrapper function (`run`)
98
+ }
99
+ });
100
+ }
101
+ /**
102
+ * runs a function immediately, tracks its reactive dependencies, and re-runs it
103
+ * synchronously whenever any of those dependencies change.
104
+ * returns a stop handle to manually stop the effect.
105
+ */
106
+ export function watchEffect(effectCallback, options = {}) {
107
+ // the wrapper function that manages the effect lifecycle (cleanup, tracking, execution)
108
+ const run = () => {
109
+ if (!effectFn.active) {
110
+ // if stopped, potentially run the callback once without tracking, though behavior might be undefined
111
+ // vue's behavior here might differ, review needed if exact compatibility matters
112
+ try {
113
+ return effectCallback();
114
+ }
115
+ catch (e) {
116
+ console.error("error in stopped watchEffect callback:", e);
117
+ // decide on return value for stopped effects that error
118
+ return undefined; // or rethrow?
119
+ }
120
+ }
121
+ const previousEffect = activeEffect;
122
+ try {
123
+ cleanupEffect(effectFn); // clean up dependencies from the previous run
124
+ setActiveEffect(effectFn); // set this effect as the one currently tracking
125
+ return effectCallback(); // execute the user's function, triggering tracks
126
+ }
127
+ finally {
128
+ setActiveEffect(previousEffect); // restore the previous active effect
129
+ }
130
+ };
131
+ // create the internal effect object
132
+ const effectFn = {
133
+ run: run,
134
+ dependencies: new Set(), // initialize empty dependencies
135
+ options: options,
136
+ active: true, // start as active
137
+ _rawCallback: effectCallback // store the original callback
138
+ };
139
+ // run the effect immediately unless the `lazy` option is true
140
+ if (!options.lazy) {
141
+ effectFn.run();
142
+ }
143
+ // create the function that stops the effect
144
+ const stopHandle = () => {
145
+ if (effectFn.active) {
146
+ cleanupEffect(effectFn); // remove from dependency lists
147
+ effectFn.active = false; // mark as inactive
148
+ // potentially clear other properties like dependencies/options if desired, but keeping them might allow restart? TBD.
149
+ }
150
+ };
151
+ // attach the effect instance to the stop handle for potential advanced usage
152
+ stopHandle.effect = effectFn;
153
+ return stopHandle;
154
+ }
package/dist/watch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { watchEffect } from './watchEffect';
1
+ import { watchEffect } from './watch-effect';
2
2
  import { traverse, deepClone } from './utils';
3
3
  /**
4
4
  * watches a reactive source (getter function or reactive object/ref)
@@ -0,0 +1,2 @@
1
+ import { EmitFunction, Path } from './types';
2
+ export declare function wrapArray<T extends any[]>(arr: T, emit: EmitFunction, path: Path): T;
@@ -0,0 +1,237 @@
1
+ import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
+ import { reactive } from './reactive';
3
+ import { wrapMap } from './wrap-map';
4
+ import { wrapSet } from './wrap-set';
5
+ import { track, trigger } from './watch-effect';
6
+ // avoid repeated typeof checks
7
+ function isObject(v) {
8
+ return v && typeof v === 'object';
9
+ }
10
+ export function wrapArray(arr, emit, path) {
11
+ // reuse existing proxy if available for performance
12
+ const cachedProxy = wrapperCache.get(arr);
13
+ if (cachedProxy)
14
+ return cachedProxy;
15
+ // cache for wrapped methods to avoid re-creating them on each call
16
+ const methodCache = {};
17
+ const proxy = new Proxy(arr, {
18
+ get(target, prop, receiver) {
19
+ track(target, prop);
20
+ if (methodCache[prop]) {
21
+ return methodCache[prop];
22
+ }
23
+ // handle specific array mutation methods that require custom logic and event emission
24
+ switch (prop) {
25
+ case 'push':
26
+ track(target, 'length');
27
+ methodCache[prop] = function (...items) {
28
+ const oldLength = target.length;
29
+ const result = target.push(...items);
30
+ const newLength = target.length;
31
+ if (items.length > 0) {
32
+ const event = {
33
+ action: 'array-push',
34
+ path: path,
35
+ key: oldLength, // start index was the old length
36
+ items: items
37
+ };
38
+ emit(event);
39
+ trigger(target, Symbol.iterator);
40
+ if (oldLength !== newLength) {
41
+ trigger(target, 'length');
42
+ }
43
+ }
44
+ return result;
45
+ };
46
+ return methodCache[prop];
47
+ case 'pop':
48
+ track(target, 'length');
49
+ methodCache[prop] = function () {
50
+ if (target.length === 0)
51
+ return undefined;
52
+ const oldLength = target.length;
53
+ const poppedIndex = oldLength - 1;
54
+ const oldValue = target[poppedIndex];
55
+ const result = target.pop();
56
+ const newLength = target.length;
57
+ const event = {
58
+ action: 'array-pop',
59
+ path: path,
60
+ key: poppedIndex,
61
+ oldValue: oldValue
62
+ };
63
+ emit(event);
64
+ trigger(target, Symbol.iterator);
65
+ if (oldLength !== newLength) {
66
+ trigger(target, 'length');
67
+ }
68
+ return result;
69
+ };
70
+ return methodCache[prop];
71
+ case 'shift':
72
+ track(target, 'length');
73
+ methodCache[prop] = function () {
74
+ if (target.length === 0)
75
+ return undefined;
76
+ const oldLength = target.length;
77
+ const oldValue = target[0];
78
+ const result = target.shift();
79
+ const newLength = target.length;
80
+ const event = {
81
+ action: 'array-shift',
82
+ path: path,
83
+ key: 0,
84
+ oldValue: oldValue
85
+ };
86
+ emit(event);
87
+ trigger(target, Symbol.iterator);
88
+ if (oldLength !== newLength) {
89
+ trigger(target, 'length');
90
+ }
91
+ return result;
92
+ };
93
+ return methodCache[prop];
94
+ case 'unshift':
95
+ track(target, 'length');
96
+ methodCache[prop] = function (...items) {
97
+ const oldLength = target.length;
98
+ const result = target.unshift(...items);
99
+ const newLength = target.length;
100
+ if (items.length > 0) {
101
+ const event = {
102
+ action: 'array-unshift',
103
+ path: path,
104
+ key: 0,
105
+ items: items
106
+ };
107
+ emit(event);
108
+ trigger(target, Symbol.iterator);
109
+ if (oldLength !== newLength) {
110
+ trigger(target, 'length');
111
+ }
112
+ }
113
+ return result;
114
+ };
115
+ return methodCache[prop];
116
+ case 'splice':
117
+ track(target, 'length');
118
+ methodCache[prop] = function (start, deleteCount, ...items) {
119
+ const oldLength = target.length;
120
+ const actualStart = start < 0 ? Math.max(target.length + start, 0) : Math.min(start, target.length);
121
+ const deleteCountNum = deleteCount === undefined ? target.length - actualStart : Number(deleteCount);
122
+ const actualDeleteCount = Math.min(deleteCountNum, target.length - actualStart);
123
+ const deletedItems = target.slice(actualStart, actualStart + actualDeleteCount);
124
+ const result = target.splice(start, deleteCountNum, ...items);
125
+ const newLength = target.length;
126
+ if (actualDeleteCount > 0 || items.length > 0) {
127
+ const event = {
128
+ action: 'array-splice',
129
+ path: path,
130
+ key: actualStart,
131
+ deleteCount: actualDeleteCount,
132
+ items: items.length > 0 ? items : undefined,
133
+ oldValues: deletedItems.length > 0 ? deletedItems : undefined
134
+ };
135
+ emit(event);
136
+ trigger(target, Symbol.iterator);
137
+ if (oldLength !== newLength) {
138
+ trigger(target, 'length');
139
+ }
140
+ }
141
+ return result;
142
+ };
143
+ return methodCache[prop];
144
+ // handle methods that rely on iteration state
145
+ case Symbol.iterator:
146
+ case 'values':
147
+ case 'keys':
148
+ case 'entries':
149
+ case 'forEach':
150
+ case 'map':
151
+ case 'filter':
152
+ case 'reduce':
153
+ case 'reduceRight':
154
+ case 'find':
155
+ case 'findIndex':
156
+ case 'every':
157
+ case 'some':
158
+ case 'join':
159
+ track(target, Symbol.iterator);
160
+ // fall through to default behavior (usually binding)
161
+ break;
162
+ case 'length':
163
+ track(target, 'length');
164
+ return Reflect.get(target, prop, receiver);
165
+ }
166
+ const value = Reflect.get(target, prop, receiver);
167
+ // determine if the property access is numeric array index access
168
+ const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(prop, 10)));
169
+ if (isNumericIndex) {
170
+ track(target, String(prop));
171
+ if (!isObject(value))
172
+ return value;
173
+ // reuse existing proxy for nested object/array if available
174
+ const cachedValueProxy = wrapperCache.get(value);
175
+ if (cachedValueProxy)
176
+ return cachedValueProxy;
177
+ // calculate the nested path for the element, optimizing with caching
178
+ const propKey = String(prop);
179
+ const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
180
+ let newPath = getPathConcat(pathKey);
181
+ if (newPath === undefined) {
182
+ newPath = path.concat(propKey);
183
+ setPathConcat(pathKey, newPath);
184
+ }
185
+ // recursively wrap nested structures
186
+ if (Array.isArray(value))
187
+ return wrapArray(value, emit, newPath);
188
+ if (value instanceof Map)
189
+ return wrapMap(value, emit, newPath);
190
+ if (value instanceof Set)
191
+ return wrapSet(value, emit, newPath);
192
+ if (value instanceof Date)
193
+ return new Date(value.getTime()); // dates are not proxied, return a copy
194
+ return reactive(value, emit, newPath);
195
+ }
196
+ // ensure functions accessed directly are bound to the original target
197
+ if (typeof value === 'function') {
198
+ return value.bind(target);
199
+ }
200
+ return value;
201
+ },
202
+ set(target, prop, value, receiver) {
203
+ const oldValue = target[prop];
204
+ // avoid unnecessary triggers if value hasn't changed
205
+ if (oldValue === value)
206
+ return true;
207
+ if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
208
+ return true;
209
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
210
+ const result = Reflect.set(target, prop, value, receiver);
211
+ const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(String(prop))));
212
+ // emit event and trigger effects only if the set was successful and wasn't intercepted by a setter
213
+ // (unless it's a direct numeric index set, which doesn't have a descriptor.set)
214
+ if (result && (!descriptor || !descriptor.set || isNumericIndex)) {
215
+ const propKey = String(prop);
216
+ const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
217
+ let newPath = getPathConcat(pathKey);
218
+ if (newPath === undefined) {
219
+ newPath = path.concat(propKey);
220
+ setPathConcat(pathKey, newPath);
221
+ }
222
+ const event = {
223
+ action: 'set',
224
+ path: newPath,
225
+ oldValue,
226
+ newValue: value
227
+ };
228
+ emit(event);
229
+ trigger(target, prop);
230
+ }
231
+ return result;
232
+ }
233
+ });
234
+ // cache the newly created proxy before returning
235
+ wrapperCache.set(arr, proxy);
236
+ return proxy;
237
+ }
@@ -0,0 +1,2 @@
1
+ import { EmitFunction, Path } from './types';
2
+ export declare function wrapMap<K, V>(map: Map<K, V>, emit: EmitFunction, path: Path): Map<K, V>;
@@ -0,0 +1,252 @@
1
+ import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
+ import { reactive } from './reactive';
3
+ import { wrapArray } from './wrap-array';
4
+ import { wrapSet } from './wrap-set';
5
+ import { track, trigger } from './watch-effect';
6
+ export function wrapMap(map, emit, path) {
7
+ // reuse existing proxy if available for performance
8
+ const cachedProxy = wrapperCache.get(map);
9
+ if (cachedProxy)
10
+ return cachedProxy;
11
+ // cache for wrapped methods to avoid re-creating them on each call
12
+ const methodCache = {};
13
+ const proxy = new Proxy(map, {
14
+ get(target, prop, receiver) {
15
+ track(target, prop);
16
+ // iteration methods need to track the iterator symbol on every access
17
+ // to re-establish dependency after effect cleanup, even when cached
18
+ if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
19
+ track(target, Symbol.iterator);
20
+ }
21
+ if (methodCache[prop]) {
22
+ return methodCache[prop];
23
+ }
24
+ if (prop === 'set') {
25
+ methodCache[prop] = function (key, value) {
26
+ const existed = target.has(key);
27
+ const oldValue = target.get(key);
28
+ const oldSize = target.size;
29
+ // avoid unnecessary work if value hasn't changed
30
+ if (oldValue === value)
31
+ return receiver;
32
+ if (oldValue && typeof oldValue === 'object' && value && typeof value === 'object' && deepEqual(oldValue, value, new WeakMap()))
33
+ return receiver;
34
+ target.set(key, value);
35
+ const newSize = target.size;
36
+ // optimize path calculation by caching concatenated paths
37
+ const pathKey = path.join('.');
38
+ let cachedPath = getPathConcat(pathKey);
39
+ if (cachedPath === undefined) {
40
+ cachedPath = path;
41
+ setPathConcat(pathKey, cachedPath);
42
+ }
43
+ const event = {
44
+ action: 'map-set',
45
+ path: cachedPath,
46
+ key,
47
+ oldValue,
48
+ newValue: value
49
+ };
50
+ emit(event);
51
+ // trigger effects based on whether it was an add or update
52
+ if (!existed) {
53
+ trigger(target, Symbol.iterator);
54
+ if (oldSize !== newSize) {
55
+ trigger(target, 'size');
56
+ }
57
+ }
58
+ else {
59
+ trigger(target, String(key));
60
+ }
61
+ return receiver;
62
+ };
63
+ return methodCache[prop];
64
+ }
65
+ if (prop === 'delete') {
66
+ methodCache[prop] = function (key) {
67
+ const existed = target.has(key);
68
+ if (!existed)
69
+ return false;
70
+ const oldValue = target.get(key);
71
+ const oldSize = target.size;
72
+ const result = target.delete(key);
73
+ const newSize = target.size;
74
+ if (result) { // only emit and trigger if delete was successful
75
+ const pathKey = path.join('.');
76
+ let cachedPath = getPathConcat(pathKey);
77
+ if (cachedPath === undefined) {
78
+ cachedPath = path;
79
+ setPathConcat(pathKey, cachedPath);
80
+ }
81
+ const event = {
82
+ action: 'map-delete',
83
+ path: cachedPath,
84
+ key,
85
+ oldValue
86
+ };
87
+ emit(event);
88
+ trigger(target, Symbol.iterator);
89
+ if (oldSize !== newSize) {
90
+ trigger(target, 'size');
91
+ }
92
+ trigger(target, String(key));
93
+ }
94
+ return result;
95
+ };
96
+ return methodCache[prop];
97
+ }
98
+ if (prop === 'clear') {
99
+ methodCache[prop] = function () {
100
+ const oldSize = target.size;
101
+ if (oldSize === 0)
102
+ return;
103
+ target.clear();
104
+ const newSize = target.size;
105
+ const event = {
106
+ action: 'map-clear',
107
+ path: path,
108
+ key: null
109
+ };
110
+ emit(event);
111
+ trigger(target, Symbol.iterator);
112
+ if (oldSize !== newSize) {
113
+ trigger(target, 'size');
114
+ }
115
+ };
116
+ return methodCache[prop];
117
+ }
118
+ if (prop === 'get') {
119
+ // return a function that tracks the specific key only when called
120
+ methodCache[prop] = function (key) {
121
+ track(target, String(key));
122
+ const value = target.get(key);
123
+ if (!value || typeof value !== 'object')
124
+ return value;
125
+ const cachedValueProxy = wrapperCache.get(value);
126
+ if (cachedValueProxy)
127
+ return cachedValueProxy;
128
+ const keyString = String(key);
129
+ const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
130
+ let newPath = getPathConcat(pathKey);
131
+ if (newPath === undefined) {
132
+ newPath = path.concat(keyString);
133
+ setPathConcat(pathKey, newPath);
134
+ }
135
+ // recursively wrap nested structures
136
+ if (value instanceof Map)
137
+ return wrapMap(value, emit, newPath);
138
+ if (value instanceof Set)
139
+ return wrapSet(value, emit, newPath);
140
+ if (Array.isArray(value))
141
+ return wrapArray(value, emit, newPath);
142
+ if (value instanceof Date)
143
+ return new Date(value.getTime()); // dates are not proxied, return a copy
144
+ return reactive(value, emit, newPath);
145
+ };
146
+ return methodCache[prop];
147
+ }
148
+ if (prop === 'has') {
149
+ track(target, Symbol.iterator);
150
+ methodCache[prop] = function (key) {
151
+ // track the specific key only when 'has' is called
152
+ track(target, String(key));
153
+ return target.has(key);
154
+ }.bind(target); // bind is still okay here, doesn't interfere with caching
155
+ return methodCache[prop];
156
+ }
157
+ // handle iteration methods
158
+ if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
159
+ track(target, Symbol.iterator);
160
+ const originalMethod = Reflect.get(target, prop, receiver);
161
+ // return custom iterators/foreach that wrap values during iteration
162
+ if (prop === 'forEach') {
163
+ methodCache[prop] = (callbackfn, thisArg) => {
164
+ // use the proxied .entries() to ensure values passed to callback are wrapped and tracked
165
+ const entriesIterator = proxy.entries();
166
+ for (const [key, value] of entriesIterator) {
167
+ callbackfn.call(thisArg, value, key, proxy);
168
+ }
169
+ };
170
+ return methodCache[prop];
171
+ }
172
+ // handle symbol.iterator, entries, values, keys by creating generator functions
173
+ methodCache[prop] = function* (...args) {
174
+ const iterator = originalMethod.apply(target, args);
175
+ for (const entry of iterator) {
176
+ let keyToWrap = entry;
177
+ let valueToWrap = entry;
178
+ let isEntry = false;
179
+ if (prop === 'entries' || prop === Symbol.iterator) {
180
+ keyToWrap = entry[0];
181
+ valueToWrap = entry[1];
182
+ isEntry = true;
183
+ }
184
+ // wrap key if it's an object
185
+ // note: reactivity on map keys can be complex/unexpected
186
+ let wrappedKey = keyToWrap;
187
+ if (isEntry && keyToWrap && typeof keyToWrap === 'object') {
188
+ const pathKey = path.length > 0 ? `${path.join('.')}.${String(keyToWrap)}` : String(keyToWrap);
189
+ let keyPath = getPathConcat(pathKey);
190
+ if (keyPath === undefined) {
191
+ keyPath = path.concat(String(keyToWrap));
192
+ setPathConcat(pathKey, keyPath);
193
+ }
194
+ // todo: decide if map keys should be deeply reactive
195
+ wrappedKey = reactive(keyToWrap, emit, keyPath);
196
+ }
197
+ // wrap value if it's an object
198
+ let wrappedValue = valueToWrap;
199
+ if (valueToWrap && typeof valueToWrap === 'object') {
200
+ const cachedValueProxy = wrapperCache.get(valueToWrap);
201
+ if (cachedValueProxy) {
202
+ wrappedValue = cachedValueProxy;
203
+ }
204
+ else {
205
+ const keyString = String(keyToWrap); // use original key for path
206
+ const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
207
+ let newPath = getPathConcat(pathKey);
208
+ if (newPath === undefined) {
209
+ newPath = path.concat(keyString);
210
+ setPathConcat(pathKey, newPath);
211
+ }
212
+ if (valueToWrap instanceof Map)
213
+ wrappedValue = wrapMap(valueToWrap, emit, newPath);
214
+ else if (valueToWrap instanceof Set)
215
+ wrappedValue = wrapSet(valueToWrap, emit, newPath);
216
+ else if (Array.isArray(valueToWrap))
217
+ wrappedValue = wrapArray(valueToWrap, emit, newPath);
218
+ else if (valueToWrap instanceof Date)
219
+ wrappedValue = new Date(valueToWrap.getTime());
220
+ else
221
+ wrappedValue = reactive(valueToWrap, emit, newPath);
222
+ }
223
+ }
224
+ if (prop === 'entries' || prop === Symbol.iterator) {
225
+ yield [wrappedKey, wrappedValue];
226
+ }
227
+ else if (prop === 'values') {
228
+ yield wrappedValue;
229
+ }
230
+ else { // keys
231
+ yield wrappedKey;
232
+ }
233
+ }
234
+ };
235
+ return methodCache[prop];
236
+ }
237
+ if (prop === 'size') {
238
+ track(target, 'size');
239
+ return target.size;
240
+ }
241
+ const value = Reflect.get(target, prop, receiver);
242
+ // Bind plain functions accessed directly (e.g., toString)
243
+ if (typeof value === 'function') {
244
+ return value.bind(target);
245
+ }
246
+ return value;
247
+ }
248
+ });
249
+ // cache the newly created proxy before returning
250
+ wrapperCache.set(map, proxy);
251
+ return proxy;
252
+ }
@@ -0,0 +1,2 @@
1
+ import { EmitFunction, Path } from './types';
2
+ export declare function wrapSet<T>(set: Set<T>, emit: EmitFunction, path: Path): Set<T>;
@@ -0,0 +1,182 @@
1
+ import { getPathConcat, setPathConcat, wrapperCache } from './utils';
2
+ import { reactive } from './reactive';
3
+ import { wrapArray } from './wrap-array';
4
+ import { wrapMap } from './wrap-map';
5
+ import { track, trigger } from './watch-effect';
6
+ export function wrapSet(set, emit, path) {
7
+ // reuse existing proxy if available for performance
8
+ const cachedProxy = wrapperCache.get(set);
9
+ if (cachedProxy)
10
+ return cachedProxy;
11
+ // cache for wrapped methods to avoid re-creating them on each call
12
+ const methodCache = {};
13
+ const proxy = new Proxy(set, {
14
+ get(target, prop, receiver) {
15
+ track(target, prop);
16
+ if (methodCache[prop]) {
17
+ return methodCache[prop];
18
+ }
19
+ if (prop === 'add') {
20
+ methodCache[prop] = function (value) {
21
+ const existed = target.has(value);
22
+ const oldSize = target.size;
23
+ // only add and trigger if the value doesn't already exist
24
+ if (!existed) {
25
+ target.add(value);
26
+ const newSize = target.size;
27
+ const event = {
28
+ action: 'set-add',
29
+ path: path,
30
+ value: value
31
+ };
32
+ emit(event);
33
+ trigger(target, Symbol.iterator);
34
+ if (oldSize !== newSize) {
35
+ trigger(target, 'size');
36
+ }
37
+ }
38
+ return receiver; // return the proxy itself for chaining
39
+ };
40
+ return methodCache[prop];
41
+ }
42
+ if (prop === 'delete') {
43
+ methodCache[prop] = function (value) {
44
+ const existed = target.has(value);
45
+ const oldSize = target.size;
46
+ if (existed) {
47
+ const oldValue = value;
48
+ const result = target.delete(value);
49
+ const newSize = target.size;
50
+ if (result) { // only emit and trigger if delete was successful
51
+ const event = {
52
+ action: 'set-delete',
53
+ path: path,
54
+ value: value,
55
+ oldValue: oldValue
56
+ };
57
+ emit(event);
58
+ trigger(target, Symbol.iterator);
59
+ if (oldSize !== newSize) {
60
+ trigger(target, 'size');
61
+ }
62
+ }
63
+ return result;
64
+ }
65
+ return false;
66
+ };
67
+ return methodCache[prop];
68
+ }
69
+ if (prop === 'clear') {
70
+ methodCache[prop] = function () {
71
+ const oldSize = target.size;
72
+ if (oldSize === 0)
73
+ return;
74
+ target.clear();
75
+ const newSize = target.size;
76
+ const event = {
77
+ action: 'set-clear',
78
+ path: path,
79
+ value: null
80
+ };
81
+ emit(event);
82
+ trigger(target, Symbol.iterator);
83
+ if (oldSize !== newSize) {
84
+ trigger(target, 'size');
85
+ }
86
+ };
87
+ return methodCache[prop];
88
+ }
89
+ if (prop === 'has') {
90
+ track(target, Symbol.iterator);
91
+ methodCache[prop] = function (value) {
92
+ // track specific primitive value when 'has' is called
93
+ // tracking object values for existence is complex and less common, handled by iteration track
94
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol') {
95
+ track(target, String(value));
96
+ }
97
+ return target.has(value);
98
+ }.bind(target); // bind is still okay here, doesn't interfere with caching
99
+ return methodCache[prop];
100
+ }
101
+ // handle iteration methods
102
+ if (prop === 'values' || prop === Symbol.iterator || prop === 'entries' || prop === 'keys' || prop === 'forEach') {
103
+ track(target, Symbol.iterator);
104
+ const originalMethod = Reflect.get(target, prop, receiver);
105
+ // return custom iterators/foreach that wrap values during iteration
106
+ if (prop === 'forEach') {
107
+ methodCache[prop] = (callbackfn, thisArg) => {
108
+ // use the proxied values() to ensure values passed to callback are wrapped and tracked
109
+ const valuesIterator = proxy.values();
110
+ for (const value of valuesIterator) {
111
+ callbackfn.call(thisArg, value, value, proxy);
112
+ }
113
+ };
114
+ return methodCache[prop];
115
+ }
116
+ // handle symbol.iterator, values, keys, entries by creating generator functions
117
+ methodCache[prop] = function* (...args) {
118
+ let index = 0; // use index for path generation if value is not primitive
119
+ const iterator = originalMethod.apply(target, args);
120
+ for (const entry of iterator) {
121
+ let valueToWrap = entry;
122
+ let mapKey = undefined; // key for entries() which yields [value, value]
123
+ if (prop === 'entries') {
124
+ mapKey = entry[0]; // for Set.entries(), key and value are the same
125
+ valueToWrap = entry[1];
126
+ }
127
+ track(target, String(index));
128
+ let wrappedValue = valueToWrap;
129
+ if (valueToWrap && typeof valueToWrap === 'object') {
130
+ const cachedValueProxy = wrapperCache.get(valueToWrap);
131
+ if (cachedValueProxy) {
132
+ wrappedValue = cachedValueProxy;
133
+ }
134
+ else {
135
+ // calculate path using index as key, as set values don't have inherent keys
136
+ const keyForPath = String(index);
137
+ const pathKey = path.length > 0 ? `${path.join('.')}.${keyForPath}` : keyForPath;
138
+ let newPath = getPathConcat(pathKey);
139
+ if (newPath === undefined) {
140
+ newPath = path.concat(keyForPath);
141
+ setPathConcat(pathKey, newPath);
142
+ }
143
+ // recursively wrap nested structures
144
+ if (valueToWrap instanceof Map)
145
+ wrappedValue = wrapMap(valueToWrap, emit, newPath);
146
+ else if (valueToWrap instanceof Set)
147
+ wrappedValue = wrapSet(valueToWrap, emit, newPath);
148
+ else if (Array.isArray(valueToWrap))
149
+ wrappedValue = wrapArray(valueToWrap, emit, newPath);
150
+ else if (valueToWrap instanceof Date)
151
+ wrappedValue = new Date(valueToWrap.getTime()); // dates are not proxied, return copy
152
+ else
153
+ wrappedValue = reactive(valueToWrap, emit, newPath);
154
+ }
155
+ }
156
+ if (prop === 'entries') {
157
+ yield [wrappedValue, wrappedValue]; // set entries yield [value, value]
158
+ }
159
+ else {
160
+ yield wrappedValue;
161
+ }
162
+ index++;
163
+ }
164
+ };
165
+ return methodCache[prop];
166
+ }
167
+ if (prop === 'size') {
168
+ track(target, 'size');
169
+ return target.size;
170
+ }
171
+ const value = Reflect.get(target, prop, receiver);
172
+ // Bind plain functions accessed directly (e.g., toString)
173
+ if (typeof value === 'function') {
174
+ return value.bind(target);
175
+ }
176
+ return value;
177
+ }
178
+ });
179
+ // cache the newly created proxy before returning
180
+ wrapperCache.set(set, proxy);
181
+ return proxy;
182
+ }
package/dist/wrapArray.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
2
  import { reactive } from './reactive';
3
- import { wrapMap } from './wrapMap';
4
- import { wrapSet } from './wrapSet';
5
- import { track, trigger } from './watchEffect';
3
+ import { wrapMap } from './wrap-map';
4
+ import { wrapSet } from './wrap-set';
5
+ import { track, trigger } from './watch-effect';
6
6
  // avoid repeated typeof checks
7
7
  function isObject(v) {
8
8
  return v && typeof v === 'object';
package/dist/wrapMap.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
2
  import { reactive } from './reactive';
3
- import { wrapArray } from './wrapArray';
4
- import { wrapSet } from './wrapSet';
5
- import { track, trigger } from './watchEffect';
3
+ import { wrapArray } from './wrap-array';
4
+ import { wrapSet } from './wrap-set';
5
+ import { track, trigger } from './watch-effect';
6
6
  export function wrapMap(map, emit, path) {
7
7
  // reuse existing proxy if available for performance
8
8
  const cachedProxy = wrapperCache.get(map);
package/dist/wrapSet.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { getPathConcat, setPathConcat, wrapperCache } from './utils';
2
2
  import { reactive } from './reactive';
3
- import { wrapArray } from './wrapArray';
4
- import { wrapMap } from './wrapMap';
5
- import { track, trigger } from './watchEffect';
3
+ import { wrapArray } from './wrap-array';
4
+ import { wrapMap } from './wrap-map';
5
+ import { track, trigger } from './watch-effect';
6
6
  export function wrapSet(set, emit, path) {
7
7
  // reuse existing proxy if available for performance
8
8
  const cachedProxy = wrapperCache.get(set);
package/package.json CHANGED
@@ -1,30 +1,34 @@
1
1
  {
2
2
  "name": "@yiin/reactive-proxy-state",
3
- "private": false,
4
- "version": "1.0.8",
5
- "description": "A simple, standalone reactivity library using Proxies",
3
+ "version": "1.0.10",
4
+ "author": "Yiin <stanislovas@yiin.lt>",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Yiin/reactive-proxy-state.git"
8
+ },
6
9
  "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
10
+ "devDependencies": {
11
+ "bun-types": "^1.2.8",
12
+ "typescript": "^5.0.4",
13
+ "vitepress": "^1.6.3",
14
+ "vue": "^3.5.13"
15
+ },
8
16
  "exports": {
9
17
  ".": {
10
18
  "types": "./dist/index.d.ts",
11
19
  "require": "./dist/index.js"
12
20
  }
13
21
  },
22
+ "bugs": {
23
+ "url": "https://github.com/Yiin/reactive-proxy-state/issues"
24
+ },
25
+ "description": "A simple, standalone reactivity library using Proxies",
14
26
  "files": [
15
27
  "dist",
16
28
  "README.md",
17
29
  "LICENSE"
18
30
  ],
19
- "scripts": {
20
- "build": "tsc",
21
- "test": "bun test",
22
- "test:watch": "bun test --watch",
23
- "test:bench": "bun --bun ./benchmarks/state-sync.bench.ts",
24
- "docs:dev": "vitepress dev docs",
25
- "docs:build": "vitepress build docs",
26
- "docs:preview": "vitepress preview docs"
27
- },
31
+ "homepage": "https://Yiin.github.io/reactive-proxy-state/",
28
32
  "keywords": [
29
33
  "state",
30
34
  "sync",
@@ -35,23 +39,19 @@
35
39
  "vue",
36
40
  "reactivity"
37
41
  ],
38
- "author": "Yiin <stanislovas@yiin.lt>",
39
42
  "license": "MIT",
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/Yiin/reactive-proxy-state.git"
43
- },
44
- "bugs": {
45
- "url": "https://github.com/Yiin/reactive-proxy-state/issues"
46
- },
47
- "homepage": "https://Yiin.github.io/reactive-proxy-state/",
43
+ "private": false,
48
44
  "publishConfig": {
49
45
  "access": "public"
50
46
  },
51
- "devDependencies": {
52
- "bun-types": "^1.2.8",
53
- "typescript": "^5.0.4",
54
- "vitepress": "^1.6.3",
55
- "vue": "^3.5.13"
56
- }
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "test": "bun test",
50
+ "test:watch": "bun test --watch",
51
+ "test:bench": "bun --bun ./benchmarks/state-sync.bench.ts",
52
+ "docs:dev": "vitepress dev docs",
53
+ "docs:build": "vitepress build docs",
54
+ "docs:preview": "vitepress preview docs"
55
+ },
56
+ "types": "dist/index.d.ts"
57
57
  }