@nerdalytics/beacon 1000.3.0 → 1000.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ count.set(5);
37
37
  ## Documentation
38
38
 
39
39
  Full documentation, API reference, and examples available at:
40
- **[github.com/nerdalytics/beacon](https://github.com/nerdalytics/beacon)**
40
+ **[nerdalytics.github.io/beacon](https://nerdalytics.github.io/beacon/)**
41
41
 
42
42
  ## License
43
43
 
@@ -16,5 +16,5 @@ declare const createSelect: <T, R>(source: ReadOnlyState<T>, selectorFn: (state:
16
16
  declare const createLens: <T, K>(source: State<T>, accessor: (state: T) => K) => State<K>;
17
17
  declare const createReadonlyState: <T>(source: State<T>) => ReadOnlyState<T>;
18
18
  declare const createProtectedState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => [ReadOnlyState<T>, WriteableState<T>];
19
- export { createDerive as derive, createEffect as effect, createLens as lens, createProtectedState as protectedState, createReadonlyState as readonlyState, createSelect as select, createState as state, executeBatch as batch, };
20
19
  export type { ReadOnlyState, State, Unsubscribe, WriteableState };
20
+ export { createDerive as derive, createEffect as effect, createLens as lens, createProtectedState as protectedState, createReadonlyState as readonlyState, createSelect as select, createState as state, executeBatch as batch, };
@@ -1 +1 @@
1
- let a=Symbol("STATE_ID"),s=null,u=new Set,d=!1,c=0,l=[],i=new Set,o=new WeakMap,f=new WeakMap,S=new WeakMap,r=new WeakMap,v=()=>{if(!d){d=!0;try{for(;0<u.size;){var e,t=u;u=new Set;for(e of t)e()}}finally{d=!1}}},n=e=>{u.delete(e);var t=f.get(e);if(t){for(var a of t)a.delete(e);t.clear(),f.delete(e)}},p=(e,l=Object.is)=>{let i=e,r=new Set,n=Symbol(),t=()=>{var a=s;if(a){r.add(a);let e=f.get(a),t=(e||(e=new Set,f.set(a,e)),e.add(r),o.get(a));t||(t=new Set,o.set(a,t)),t.add(n)}return i};return t.set=e=>{if(!l(i,e)){var t=s;if(t)if(o.get(t)?.has(n)&&!S.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(i=e,0!==r.size){for(var a of r)u.add(a);0!==c||d||v()}}},t.update=e=>{t.set(e(i))},t[a]=n,t},g=e=>{let a=()=>{if(!i.has(a)){i.add(a);var t=s;try{if(n(a),s=a,o.set(a,new Set),t){S.set(a,t);let e=r.get(t);e||(e=new Set,r.set(t,e)),e.add(a)}e()}finally{s=t,i.delete(a)}}};if(0===c)a();else{if(s){var t=s;S.set(a,t);let e=r.get(t);e||(e=new Set,r.set(t,e)),e.add(a)}l.push(a)}return()=>{n(a),u.delete(a),i.delete(a),o.delete(a);var e=S.get(a),e=(e&&(e=r.get(e))&&e.delete(a),S.delete(a),r.get(a));if(e){for(var t of e)n(t);e.clear(),r.delete(a)}}};var e=e=>{c++;try{return e()}catch(e){throw 1===c&&(u.clear(),l.length=0),e}finally{if(0===--c){if(0<l.length){var t,e=l;l=[];for(t of e)t()}0<u.size&&!d&&v()}}},t=e=>{let t={cachedValue:void 0,computeFn:e,initialized:!1,valueState:p(void 0)};return g(function(){var e=t.computeFn();t.initialized&&Object.is(t.cachedValue,e)||(t.cachedValue=e,t.valueState.set(e)),t.initialized=!0}),function(){return t.initialized||(t.cachedValue=t.computeFn(),t.initialized=!0,t.valueState.set(t.cachedValue)),t.valueState()}},h=(e,t,a=Object.is)=>{let l={equalityFn:a,initialized:!1,lastSelectedValue:void 0,lastSourceValue:void 0,selectorFn:t,source:e,valueState:p(void 0)};return g(function(){var e=l.source();l.initialized&&Object.is(l.lastSourceValue,e)||(l.lastSourceValue=e,e=l.selectorFn(e),l.initialized&&void 0!==l.lastSelectedValue&&l.equalityFn(l.lastSelectedValue,e))||(l.lastSelectedValue=e,l.valueState.set(e),l.initialized=!0)}),function(){return l.initialized||(l.lastSourceValue=l.source(),l.lastSelectedValue=l.selectorFn(l.lastSourceValue),l.valueState.set(l.lastSelectedValue),l.initialized=!0),l.valueState()}};let y=(e,t,a)=>{e=[...e];return e[t]=a,e},w=(e,t,a)=>{e={...e};return e[t]=a,e},V=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},z=(e,t,a)=>{var l=Number(t[0]);if(1===t.length)return y(e,l,a);var i=[...e],t=t.slice(1),r=t[0];let n=e[l];return null==n&&(n=void 0!==r?V(r):{}),i[l]=m(n,t,a),i},b=(e,t,a)=>{var l=t[0];if(void 0===l)return e;if(1===t.length)return w(e,l,a);var t=t.slice(1),i=t[0];let r=e[l];null==r&&(r=void 0!==i?V(i):{});i={...e};return i[l]=m(r,t,a),i},m=(e,t,a)=>0===t.length?a:null==e?m({},t,a):void 0===t[0]?e:(Array.isArray(e)?z:b)(e,t,a);var F=(e,t)=>{let i={accessor:t,isUpdating:!1,lensState:null,originalSet:null,path:[],source:e};return i.path=(()=>{let a=[],l=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||a.push(t),l)});try{i.accessor(l)}catch{}return a})(),i.lensState=p(i.accessor(i.source())),i.originalSet=i.lensState.set,g(function(){if(!i.isUpdating){i.isUpdating=!0;try{i.lensState.set(i.accessor(i.source()))}finally{i.isUpdating=!1}}}),i.lensState.set=function(t){if(!i.isUpdating){i.isUpdating=!0;try{i.originalSet(t),i.source.update(e=>m(e,i.path,t))}finally{i.isUpdating=!1}}},i.lensState.update=function(e){i.lensState.set(e(i.lensState()))},i.lensState};let U=e=>()=>e();var j=(e,t=Object.is)=>{let a=p(e,t);return[()=>U(a)(),{set:e=>a.set(e),update:e=>a.update(e)}]};export{t as derive,g as effect,F as lens,j as protectedState,U as readonlyState,h as select,p as state,e as batch};
1
+ let r=Symbol("STATE_ID"),f=null,u=new Set,s=!1,d=0,a=[],l=new Set,o=new WeakMap,v=new WeakMap,c=new WeakMap,i=new WeakMap,y=(e,t,r)=>{let n=e.get(t);return n||(n=r(),e.set(t,n)),n},p=()=>{if(!s){s=!0;try{for(;0<u.size;){var e,t=u;u=new Set;for(e of t)e()}}finally{s=!1}}},w=e=>{u.delete(e);var t=v.get(e);if(t){for(var r of t)r.delete(e);t.clear(),v.delete(e)}},g=e=>{w(e),l.delete(e),o.delete(e);var t=c.get(e),t=(t&&(t=i.get(t))&&t.delete(e),c.delete(e),i.get(e));if(t){for(var r of t)g(r);t.clear(),i.delete(e)}},h=(e,n=Object.is)=>{let a=e,l=new Set,i=Symbol(),t=()=>{var e=f;return e&&(l.add(e),y(v,e,()=>new Set).add(l),y(o,e,()=>new Set).add(i)),a};return t.set=e=>{if(!n(a,e)){var t=f;if(t)if(o.get(t)?.has(i)&&!c.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(a=e,0!==l.size){for(var r of l)u.add(r);0!==d||s||p()}}},t.update=e=>{t.set(e(a))},t[r]=i,t},S=r=>{let n=()=>{if(!l.has(n)){l.add(n);var e=f;try{w(n),f=n;var t=o.get(n);t?t.clear():o.set(n,new Set),e&&(c.set(n,e),y(i,e,()=>new Set).add(n)),r()}finally{f=e,l.delete(n)}}};var e;return 0===d?n():(f&&(e=f,c.set(n,e),y(i,e,()=>new Set).add(n)),a.push(n)),()=>{g(n)}};var e=e=>{d++;try{return e()}catch(e){throw 1===d&&(u.clear(),a.length=0),e}finally{if(0===--d){if(0<a.length){var t,e=a;a=[];for(t of e)t()}0<u.size&&!s&&p()}}},t=t=>{let r=void 0,n=!1,a=h(void 0);return S(function(){var e=t();n&&Object.is(r,e)||(r=e,a.set(e)),n=!0}),function(){return n||(r=t(),n=!0,a.set(r)),a()}},n=(t,r,n=Object.is)=>{let a=!1,l,i,f=h(void 0);return S(function(){var e=t();a&&Object.is(i,e)||(i=e,e=r(e),a&&void 0!==l&&n(l,e))||(l=e,f.set(e),a=!0)}),function(){return a||(i=t(),l=r(i),f.set(l),a=!0),f()}};let b=(e,t,r)=>{e=[...e];return e[t]=r,e},m=(e,t,r)=>{e={...e};return e[t]=r,e},j=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},N=(n,a,l,i)=>{if(l>=a.length)return i;if(null==n)return N({},a,l,i);var f=a[l];if(void 0===f)return n;if(Array.isArray(n)){var u=Number(f);if(l===a.length-1)return b(n,u,i);var s=[...n];let e=l+1,t=a[e],r=n[u];return null==r&&(r=void 0===t?{}:j(t)),s[u]=N(r,a,e,i),s}u=n;if(l===a.length-1)return m(u,f,i);let e=l+1,t=a[e],r=u[f];null==r&&(r=void 0===t?{}:j(t));s={...u};return s[f]=N(r,a,e,i),s};var O=(e,t)=>{let r=!1;let n=(()=>{let r=[],n=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||r.push(t),n)});try{t(n)}catch{}return r})(),a=h(t(e())),l=a.set;return S(function(){if(!r){r=!0;try{a.set(t(e()))}finally{r=!1}}}),a.set=function(t){if(!r){r=!0;try{l(t),e.update(e=>N(e,n,0,t))}finally{r=!1}}},a.update=function(e){a.set(e(a()))},a};let k=e=>()=>e();var M=(e,t=Object.is)=>{let r=h(e,t);return[k(r),{set:e=>r.set(e),update:e=>r.update(e)}]};export{t as derive,S as effect,O as lens,M as protectedState,k as readonlyState,n as select,h as state,e as batch};
package/package.json CHANGED
@@ -13,15 +13,15 @@
13
13
  },
14
14
  "exports": {
15
15
  ".": {
16
+ "types": "./dist/src/index.d.ts",
17
+ "typescript": "./src/index.ts",
16
18
  "import": {
17
- "default": "./dist/src/index.min.js",
18
- "types": "./dist/src/index.d.ts"
19
+ "types": "./dist/src/index.d.ts",
20
+ "default": "./dist/src/index.min.js"
19
21
  },
20
22
  "require": {
21
23
  "default": "./dist/src/index.min.js"
22
- },
23
- "types": "./dist/src/index.d.ts",
24
- "typescript": "./src/index.ts"
24
+ }
25
25
  }
26
26
  },
27
27
  "files": [
@@ -56,7 +56,7 @@
56
56
  "url": "git+https://github.com/nerdalytics/beacon.git"
57
57
  },
58
58
  "scripts": {
59
- "benchmark": "node --expose-gc scripts/benchmark.ts",
59
+ "benchmark": "node --expose-gc scripts/benchmark.ts -R 3",
60
60
  "benchmark:naiv": "node scripts/naiv-benchmark.ts",
61
61
  "build": "npm run build:lts",
62
62
  "build:lts": "tsc -p tsconfig.lts.json",
@@ -94,5 +94,5 @@
94
94
  "sideEffects": false,
95
95
  "type": "module",
96
96
  "types": "dist/src/index.d.ts",
97
- "version": "1000.3.0"
97
+ "version": "1000.3.1"
98
98
  }
package/src/index.ts CHANGED
@@ -30,6 +30,15 @@ const subscriberDependencies: WeakMap<Subscriber, Set<Set<Subscriber>>> = new We
30
30
  const parentSubscriber: WeakMap<Subscriber, Subscriber> = new WeakMap<Subscriber, Subscriber>()
31
31
  const childSubscribers: WeakMap<Subscriber, Set<Subscriber>> = new WeakMap<Subscriber, Set<Subscriber>>()
32
32
 
33
+ const getOrCreate = <K extends object, V>(map: WeakMap<K, V>, key: K, factory: () => V): V => {
34
+ let value = map.get(key)
35
+ if (!value) {
36
+ value = factory()
37
+ map.set(key, value)
38
+ }
39
+ return value
40
+ }
41
+
33
42
  const notifySubscribers = (): void => {
34
43
  if (isNotifying) {
35
44
  return
@@ -64,9 +73,30 @@ const cleanupEffect = (effect: Subscriber): void => {
64
73
  }
65
74
  }
66
75
 
67
- /**
68
- * Creates a reactive state container with the provided initial value.
69
- */
76
+ const disposeEffect = (effect: Subscriber): void => {
77
+ cleanupEffect(effect)
78
+ activeSubscribers.delete(effect)
79
+ stateTracking.delete(effect)
80
+
81
+ const parent = parentSubscriber.get(effect)
82
+ if (parent) {
83
+ const siblings = childSubscribers.get(parent)
84
+ if (siblings) {
85
+ siblings.delete(effect)
86
+ }
87
+ }
88
+ parentSubscriber.delete(effect)
89
+
90
+ const children = childSubscribers.get(effect)
91
+ if (children) {
92
+ for (const child of children) {
93
+ disposeEffect(child)
94
+ }
95
+ children.clear()
96
+ childSubscribers.delete(effect)
97
+ }
98
+ }
99
+
70
100
  const createState = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = Object.is): State<T> => {
71
101
  let value = initialValue
72
102
  const subscribers = new Set<Subscriber>()
@@ -77,19 +107,9 @@ const createState = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = O
77
107
  if (currentEffect) {
78
108
  subscribers.add(currentEffect)
79
109
 
80
- let dependencies = subscriberDependencies.get(currentEffect)
81
- if (!dependencies) {
82
- dependencies = new Set()
83
- subscriberDependencies.set(currentEffect, dependencies)
84
- }
85
- dependencies.add(subscribers)
110
+ getOrCreate(subscriberDependencies, currentEffect, () => new Set()).add(subscribers)
86
111
 
87
- let readStates = stateTracking.get(currentEffect)
88
- if (!readStates) {
89
- readStates = new Set()
90
- stateTracking.set(currentEffect, readStates)
91
- }
92
- readStates.add(stateId)
112
+ getOrCreate(stateTracking, currentEffect, () => new Set()).add(stateId)
93
113
  }
94
114
  return value
95
115
  }
@@ -130,9 +150,6 @@ const createState = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = O
130
150
  return get as State<T>
131
151
  }
132
152
 
133
- /**
134
- * Registers a function to run whenever its reactive dependencies change.
135
- */
136
153
  const createEffect = (fn: () => void): Unsubscribe => {
137
154
  const runEffect = (): void => {
138
155
  if (activeSubscribers.has(runEffect)) {
@@ -146,16 +163,16 @@ const createEffect = (fn: () => void): Unsubscribe => {
146
163
  cleanupEffect(runEffect)
147
164
 
148
165
  currentSubscriber = runEffect
149
- stateTracking.set(runEffect, new Set())
166
+ const existingStates = stateTracking.get(runEffect)
167
+ if (existingStates) {
168
+ existingStates.clear()
169
+ } else {
170
+ stateTracking.set(runEffect, new Set())
171
+ }
150
172
 
151
173
  if (parentEffect) {
152
174
  parentSubscriber.set(runEffect, parentEffect)
153
- let children = childSubscribers.get(parentEffect)
154
- if (!children) {
155
- children = new Set()
156
- childSubscribers.set(parentEffect, children)
157
- }
158
- children.add(runEffect)
175
+ getOrCreate(childSubscribers, parentEffect, () => new Set()).add(runEffect)
159
176
  }
160
177
 
161
178
  fn()
@@ -171,46 +188,17 @@ const createEffect = (fn: () => void): Unsubscribe => {
171
188
  if (currentSubscriber) {
172
189
  const parent = currentSubscriber
173
190
  parentSubscriber.set(runEffect, parent)
174
- let children = childSubscribers.get(parent)
175
- if (!children) {
176
- children = new Set()
177
- childSubscribers.set(parent, children)
178
- }
179
- children.add(runEffect)
191
+ getOrCreate(childSubscribers, parent, () => new Set()).add(runEffect)
180
192
  }
181
193
 
182
194
  deferredEffectCreations.push(runEffect)
183
195
  }
184
196
 
185
197
  return (): void => {
186
- cleanupEffect(runEffect)
187
- pendingSubscribers.delete(runEffect)
188
- activeSubscribers.delete(runEffect)
189
- stateTracking.delete(runEffect)
190
-
191
- const parent = parentSubscriber.get(runEffect)
192
- if (parent) {
193
- const siblings = childSubscribers.get(parent)
194
- if (siblings) {
195
- siblings.delete(runEffect)
196
- }
197
- }
198
- parentSubscriber.delete(runEffect)
199
-
200
- const children = childSubscribers.get(runEffect)
201
- if (children) {
202
- for (const child of children) {
203
- cleanupEffect(child)
204
- }
205
- children.clear()
206
- childSubscribers.delete(runEffect)
207
- }
198
+ disposeEffect(runEffect)
208
199
  }
209
200
  }
210
201
 
211
- /**
212
- * Groups multiple state updates to trigger effects only once at the end.
213
- */
214
202
  const executeBatch = <T>(fn: () => T): T => {
215
203
  batchDepth++
216
204
  try {
@@ -240,87 +228,69 @@ const executeBatch = <T>(fn: () => T): T => {
240
228
  }
241
229
  }
242
230
 
243
- /**
244
- * Creates a read-only computed value that updates when its dependencies change.
245
- */
246
231
  const createDerive = <T>(computeFn: () => T): ReadOnlyState<T> => {
247
- const container = {
248
- cachedValue: undefined as unknown as T,
249
- computeFn,
250
- initialized: false,
251
- valueState: createState<T | undefined>(undefined),
252
- }
232
+ let cachedValue: T = undefined as unknown as T
233
+ let initialized = false
234
+ const valueState = createState<T | undefined>(undefined)
253
235
 
254
236
  createEffect(function deriveEffect(): void {
255
- const newValue = container.computeFn()
237
+ const newValue = computeFn()
256
238
 
257
- if (!(container.initialized && Object.is(container.cachedValue, newValue))) {
258
- container.cachedValue = newValue
259
- container.valueState.set(newValue)
239
+ if (!(initialized && Object.is(cachedValue, newValue))) {
240
+ cachedValue = newValue
241
+ valueState.set(newValue)
260
242
  }
261
243
 
262
- container.initialized = true
244
+ initialized = true
263
245
  })
264
246
 
265
247
  return function deriveGetter(): T {
266
- if (!container.initialized) {
267
- container.cachedValue = container.computeFn()
268
- container.initialized = true
269
- container.valueState.set(container.cachedValue)
248
+ if (!initialized) {
249
+ cachedValue = computeFn()
250
+ initialized = true
251
+ valueState.set(cachedValue)
270
252
  }
271
- return container.valueState() as T
253
+ return valueState() as T
272
254
  }
273
255
  }
274
256
 
275
- /**
276
- * Creates an efficient subscription to a subset of a state value.
277
- */
278
257
  const createSelect = <T, R>(
279
258
  source: ReadOnlyState<T>,
280
259
  selectorFn: (state: T) => R,
281
260
  equalityFn: (a: R, b: R) => boolean = Object.is
282
261
  ): ReadOnlyState<R> => {
283
- const container = {
284
- equalityFn,
285
- initialized: false,
286
- lastSelectedValue: undefined as R | undefined,
287
- lastSourceValue: undefined as T | undefined,
288
- selectorFn,
289
- source,
290
- valueState: createState<R | undefined>(undefined),
291
- }
262
+ let initialized = false
263
+ let lastSelectedValue: R | undefined
264
+ let lastSourceValue: T | undefined
265
+ const valueState = createState<R | undefined>(undefined)
292
266
 
293
267
  createEffect(function selectEffect(): void {
294
- const sourceValue = container.source()
268
+ const sourceValue = source()
295
269
 
296
- if (container.initialized && Object.is(container.lastSourceValue, sourceValue)) {
270
+ if (initialized && Object.is(lastSourceValue, sourceValue)) {
297
271
  return
298
272
  }
299
273
 
300
- container.lastSourceValue = sourceValue
301
- const newSelectedValue = container.selectorFn(sourceValue)
274
+ lastSourceValue = sourceValue
275
+ const newSelectedValue = selectorFn(sourceValue)
302
276
 
303
- if (
304
- container.initialized &&
305
- container.lastSelectedValue !== undefined &&
306
- container.equalityFn(container.lastSelectedValue, newSelectedValue)
307
- ) {
277
+ if (initialized && lastSelectedValue !== undefined && equalityFn(lastSelectedValue, newSelectedValue)) {
308
278
  return
309
279
  }
310
280
 
311
- container.lastSelectedValue = newSelectedValue
312
- container.valueState.set(newSelectedValue)
313
- container.initialized = true
281
+ lastSelectedValue = newSelectedValue
282
+ valueState.set(newSelectedValue)
283
+ initialized = true
314
284
  })
315
285
 
316
286
  return function selectGetter(): R {
317
- if (!container.initialized) {
318
- container.lastSourceValue = container.source()
319
- container.lastSelectedValue = container.selectorFn(container.lastSourceValue)
320
- container.valueState.set(container.lastSelectedValue)
321
- container.initialized = true
287
+ if (!initialized) {
288
+ lastSourceValue = source()
289
+ lastSelectedValue = selectorFn(lastSourceValue)
290
+ valueState.set(lastSelectedValue)
291
+ initialized = true
322
292
  }
323
- return container.valueState() as R
293
+ return valueState() as R
324
294
  }
325
295
  }
326
296
 
@@ -352,92 +322,65 @@ const createContainer = (key: string | number): Record<string | number, unknown>
352
322
  return isArrayKey ? [] : {}
353
323
  }
354
324
 
355
- // Helper for handling array path updates
356
- const updateArrayPath = <V>(array: unknown[], pathSegments: (string | number)[], value: V): unknown[] => {
357
- const index = Number(pathSegments[0])
358
-
359
- if (pathSegments.length === 1) {
360
- return updateArrayItem(array, index, value)
325
+ const setValueAtPath = <V, O>(obj: O, pathSegments: (string | number)[], depth: number, value: V): O => {
326
+ if (depth >= pathSegments.length) {
327
+ return value as unknown as O
361
328
  }
362
329
 
363
- const copy = [
364
- ...array,
365
- ]
366
- const nextPathSegments = pathSegments.slice(1)
367
- const nextKey = nextPathSegments[0]
368
-
369
- let nextValue = array[index]
370
- if (nextValue === undefined || nextValue === null) {
371
- nextValue = nextKey !== undefined ? createContainer(nextKey) : {}
330
+ if (obj === undefined || obj === null) {
331
+ return setValueAtPath({} as O, pathSegments, depth, value)
372
332
  }
373
333
 
374
- copy[index] = setValueAtPath(nextValue, nextPathSegments, value)
375
- return copy
376
- }
377
-
378
- // Helper for handling object path updates
379
- const updateObjectPath = <V>(
380
- obj: Record<string | number, unknown>,
381
- pathSegments: (string | number)[],
382
- value: V
383
- ): Record<string | number, unknown> => {
384
- const currentKey = pathSegments[0]
334
+ const currentKey = pathSegments[depth]
385
335
  if (currentKey === undefined) {
386
336
  return obj
387
337
  }
388
338
 
389
- if (pathSegments.length === 1) {
390
- return updateShallowProperty(obj, currentKey, value)
391
- }
339
+ if (Array.isArray(obj)) {
340
+ const index = Number(currentKey)
392
341
 
393
- const nextPathSegments = pathSegments.slice(1)
394
- const nextKey = nextPathSegments[0]
342
+ if (depth === pathSegments.length - 1) {
343
+ return updateArrayItem(obj, index, value) as unknown as O
344
+ }
395
345
 
396
- let currentValue = obj[currentKey]
397
- if (currentValue === undefined || currentValue === null) {
398
- currentValue = nextKey !== undefined ? createContainer(nextKey) : {}
399
- }
346
+ const copy = [
347
+ ...obj,
348
+ ]
349
+ const nextDepth = depth + 1
350
+ const nextKey = pathSegments[nextDepth]
400
351
 
401
- const result = {
402
- ...obj,
403
- }
404
- result[currentKey] = setValueAtPath(currentValue, nextPathSegments, value)
405
- return result
406
- }
352
+ let nextValue = obj[index]
353
+ if (nextValue === undefined || nextValue === null) {
354
+ nextValue = nextKey === undefined ? {} : createContainer(nextKey)
355
+ }
407
356
 
408
- const setValueAtPath = <V, O>(obj: O, pathSegments: (string | number)[], value: V): O => {
409
- if (pathSegments.length === 0) {
410
- return value as unknown as O
357
+ copy[index] = setValueAtPath(nextValue, pathSegments, nextDepth, value)
358
+ return copy as unknown as O
411
359
  }
412
360
 
413
- if (obj === undefined || obj === null) {
414
- return setValueAtPath({} as O, pathSegments, value)
415
- }
361
+ const record = obj as Record<string | number, unknown>
416
362
 
417
- const currentKey = pathSegments[0]
418
- if (currentKey === undefined) {
419
- return obj
363
+ if (depth === pathSegments.length - 1) {
364
+ return updateShallowProperty(record, currentKey, value) as unknown as O
420
365
  }
421
366
 
422
- if (Array.isArray(obj)) {
423
- return updateArrayPath(obj, pathSegments, value) as unknown as O
367
+ const nextDepth = depth + 1
368
+ const nextKey = pathSegments[nextDepth]
369
+
370
+ let currentValue = record[currentKey]
371
+ if (currentValue === undefined || currentValue === null) {
372
+ currentValue = nextKey === undefined ? {} : createContainer(nextKey)
424
373
  }
425
374
 
426
- return updateObjectPath(obj as Record<string | number, unknown>, pathSegments, value) as unknown as O
375
+ const result = {
376
+ ...record,
377
+ }
378
+ result[currentKey] = setValueAtPath(currentValue, pathSegments, nextDepth, value)
379
+ return result as unknown as O
427
380
  }
428
381
 
429
- /**
430
- * Creates a lens for direct updates to nested properties of a state.
431
- */
432
382
  const createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> => {
433
- const container = {
434
- accessor,
435
- isUpdating: false,
436
- lensState: null as unknown as State<K>,
437
- originalSet: null as unknown as (value: K) => void,
438
- path: [] as (string | number)[],
439
- source,
440
- }
383
+ let isUpdating = false
441
384
 
442
385
  const extractPath = (): (string | number)[] => {
443
386
  const pathCollector: (string | number)[] = []
@@ -454,7 +397,7 @@ const createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K>
454
397
  )
455
398
 
456
399
  try {
457
- container.accessor(proxy as unknown as T)
400
+ accessor(proxy as unknown as T)
458
401
  } catch {
459
402
  // Ignore errors, we're just collecting the path
460
403
  }
@@ -462,57 +405,49 @@ const createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K>
462
405
  return pathCollector
463
406
  }
464
407
 
465
- container.path = extractPath()
466
-
467
- container.lensState = createState<K>(container.accessor(container.source()))
468
- container.originalSet = container.lensState.set
408
+ const path = extractPath()
409
+ const lensState = createState<K>(accessor(source()))
410
+ const originalSet = lensState.set
469
411
 
470
412
  createEffect(function lensEffect(): void {
471
- if (container.isUpdating) {
413
+ if (isUpdating) {
472
414
  return
473
415
  }
474
416
 
475
- container.isUpdating = true
417
+ isUpdating = true
476
418
  try {
477
- container.lensState.set(container.accessor(container.source()))
419
+ lensState.set(accessor(source()))
478
420
  } finally {
479
- container.isUpdating = false
421
+ isUpdating = false
480
422
  }
481
423
  })
482
424
 
483
- container.lensState.set = function lensSet(value: K): void {
484
- if (container.isUpdating) {
425
+ lensState.set = function lensSet(value: K): void {
426
+ if (isUpdating) {
485
427
  return
486
428
  }
487
429
 
488
- container.isUpdating = true
430
+ isUpdating = true
489
431
  try {
490
- container.originalSet(value)
491
-
492
- container.source.update((current: T): T => setValueAtPath(current, container.path, value))
432
+ originalSet(value)
433
+ source.update((current: T): T => setValueAtPath(current, path, 0, value))
493
434
  } finally {
494
- container.isUpdating = false
435
+ isUpdating = false
495
436
  }
496
437
  }
497
438
 
498
- container.lensState.update = function lensUpdate(fn: (value: K) => K): void {
499
- container.lensState.set(fn(container.lensState()))
439
+ lensState.update = function lensUpdate(fn: (value: K) => K): void {
440
+ lensState.set(fn(lensState()))
500
441
  }
501
442
 
502
- return container.lensState
443
+ return lensState
503
444
  }
504
445
 
505
- /**
506
- * Creates a read-only view of a state, hiding mutation methods.
507
- */
508
446
  const createReadonlyState =
509
447
  <T>(source: State<T>): ReadOnlyState<T> =>
510
448
  (): T =>
511
449
  source()
512
450
 
513
- /**
514
- * Creates a state with access control, returning a tuple of reader and writer.
515
- */
516
451
  const createProtectedState = <T>(
517
452
  initialValue: T,
518
453
  equalityFn: (a: T, b: T) => boolean = Object.is
@@ -521,8 +456,9 @@ const createProtectedState = <T>(
521
456
  WriteableState<T>,
522
457
  ] => {
523
458
  const fullState = createState(initialValue, equalityFn)
459
+ const reader = createReadonlyState(fullState)
524
460
  return [
525
- (): T => createReadonlyState(fullState)(),
461
+ reader,
526
462
  {
527
463
  set: (value: T): void => fullState.set(value),
528
464
  update: (fn: (value: T) => T): void => fullState.update(fn),
@@ -530,6 +466,7 @@ const createProtectedState = <T>(
530
466
  ]
531
467
  }
532
468
 
469
+ export type { ReadOnlyState, State, Unsubscribe, WriteableState }
533
470
  export {
534
471
  createDerive as derive,
535
472
  createEffect as effect,
@@ -540,5 +477,3 @@ export {
540
477
  createState as state,
541
478
  executeBatch as batch,
542
479
  }
543
-
544
- export type { ReadOnlyState, State, Unsubscribe, WriteableState }