@nerdalytics/beacon 1000.2.1 → 1000.2.3
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 +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.min.js +1 -1
- package/package.json +77 -60
- package/src/index.ts +101 -67
- package/dist/src/index.js +0 -379
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/nerdalytics/beacon/blob/trunk/LICENSE)
|
|
6
6
|
[](https://www.npmjs.com/package/@nerdalytics/beacon)
|
|
7
|
+
[](https://badge.socket.dev/npm/package/@nerdalytics/beacon/latest)
|
|
7
8
|
|
|
8
9
|
[](https://nodejs.org/)
|
|
9
10
|
[](https://typescriptlang.org/)
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let
|
|
1
|
+
let i=Symbol("STATE_ID"),a=(e,t=Object.is)=>l.createState(e,t);var e=e=>l.createEffect(e),t=e=>l.executeBatch(e),r=e=>l.createDerive(e),s=(e,t,r=Object.is)=>l.createSelect(e,t,r);let c=e=>()=>e();var n=(e,t=Object.is)=>{let r=a(e,t);return[()=>c(r)(),{set:e=>r.set(e),update:e=>r.update(e)}]},u=(e,t)=>l.createLens(e,t);class l{static currentSubscriber=null;static pendingSubscribers=new Set;static isNotifying=!1;static batchDepth=0;static deferredEffectCreations=[];static activeSubscribers=new Set;static stateTracking=new WeakMap;static subscriberDependencies=new WeakMap;static parentSubscriber=new WeakMap;static childSubscribers=new WeakMap;value;subscribers=new Set;stateId=Symbol();equalityFn;constructor(e,t=Object.is){this.value=e,this.equalityFn=t}static createState=(e,t=Object.is)=>{let r=new l(e,t);e=()=>r.get();return e.set=e=>r.set(e),e.update=e=>r.update(e),e[i]=r.stateId,e};get=()=>{var r=l.currentSubscriber;if(r){this.subscribers.add(r);let e=l.subscriberDependencies.get(r),t=(e||(e=new Set,l.subscriberDependencies.set(r,e)),e.add(this.subscribers),l.stateTracking.get(r));t||(t=new Set,l.stateTracking.set(r,t)),t.add(this.stateId)}return this.value};set=e=>{if(!this.equalityFn(this.value,e)){var t=l.currentSubscriber;if(t)if(l.stateTracking.get(t)?.has(this.stateId)&&!l.parentSubscriber.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(this.value=e,0!==this.subscribers.size){for(var r of this.subscribers)l.pendingSubscribers.add(r);0!==l.batchDepth||l.isNotifying||l.notifySubscribers()}}};update=e=>{this.set(e(this.value))};static createEffect=e=>{let r=()=>{if(!l.activeSubscribers.has(r)){l.activeSubscribers.add(r);var t=l.currentSubscriber;try{if(l.cleanupEffect(r),l.currentSubscriber=r,l.stateTracking.set(r,new Set),t){l.parentSubscriber.set(r,t);let e=l.childSubscribers.get(t);e||(e=new Set,l.childSubscribers.set(t,e)),e.add(r)}e()}finally{l.currentSubscriber=t,l.activeSubscribers.delete(r)}}};if(0===l.batchDepth)r();else{if(l.currentSubscriber){var t=l.currentSubscriber;l.parentSubscriber.set(r,t);let e=l.childSubscribers.get(t);e||(e=new Set,l.childSubscribers.set(t,e)),e.add(r)}l.deferredEffectCreations.push(r)}return()=>{l.cleanupEffect(r),l.pendingSubscribers.delete(r),l.activeSubscribers.delete(r),l.stateTracking.delete(r);var e=l.parentSubscriber.get(r),e=(e&&(e=l.childSubscribers.get(e))&&e.delete(r),l.parentSubscriber.delete(r),l.childSubscribers.get(r));if(e){for(var t of e)l.cleanupEffect(t);e.clear(),l.childSubscribers.delete(r)}}};static executeBatch=e=>{l.batchDepth++;try{return e()}catch(e){throw 1===l.batchDepth&&(l.pendingSubscribers.clear(),l.deferredEffectCreations.length=0),e}finally{if(l.batchDepth--,0===l.batchDepth){if(0<l.deferredEffectCreations.length){var t,e=[...l.deferredEffectCreations];l.deferredEffectCreations.length=0;for(t of e)t()}0<l.pendingSubscribers.size&&!l.isNotifying&&l.notifySubscribers()}}};static createDerive=e=>{let t={cachedValue:void 0,computeFn:e,initialized:!1,valueState:l.createState(void 0)};return l.createEffect(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()}};static createSelect=(e,t,r=Object.is)=>{let i={equalityFn:r,initialized:!1,lastSelectedValue:void 0,lastSourceValue:void 0,selectorFn:t,source:e,valueState:l.createState(void 0)};return l.createEffect(function(){var e=i.source();i.initialized&&Object.is(i.lastSourceValue,e)||(i.lastSourceValue=e,e=i.selectorFn(e),i.initialized&&void 0!==i.lastSelectedValue&&i.equalityFn(i.lastSelectedValue,e))||(i.lastSelectedValue=e,i.valueState.set(e),i.initialized=!0)}),function(){return i.initialized||(i.lastSourceValue=i.source(),i.lastSelectedValue=i.selectorFn(i.lastSourceValue),i.valueState.set(i.lastSelectedValue),i.initialized=!0),i.valueState()}};static createLens=(e,t)=>{let a={accessor:t,isUpdating:!1,lensState:null,originalSet:null,path:[],source:e};return a.path=(()=>{let r=[],i=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||r.push(t),i)});try{a.accessor(i)}catch{}return r})(),a.lensState=l.createState(a.accessor(a.source())),a.originalSet=a.lensState.set,l.createEffect(function(){if(!a.isUpdating){a.isUpdating=!0;try{a.lensState.set(a.accessor(a.source()))}finally{a.isUpdating=!1}}}),a.lensState.set=function(t){if(!a.isUpdating){a.isUpdating=!0;try{a.originalSet(t),a.source.update(e=>p(e,a.path,t))}finally{a.isUpdating=!1}}},a.lensState.update=function(e){a.lensState.set(e(a.lensState()))},a.lensState};static notifySubscribers=()=>{if(!l.isNotifying){l.isNotifying=!0;try{for(;0<l.pendingSubscribers.size;){var e,t=Array.from(l.pendingSubscribers);l.pendingSubscribers.clear();for(e of t)e()}}finally{l.isNotifying=!1}}};static cleanupEffect=e=>{l.pendingSubscribers.delete(e);var t=l.subscriberDependencies.get(e);if(t){for(var r of t)r.delete(e);t.clear(),l.subscriberDependencies.delete(e)}}}let b=(e,t,r)=>{e=[...e];return e[t]=r,e},d=(e,t,r)=>{e={...e};return e[t]=r,e},f=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},S=(e,t,r)=>{var i=Number(t[0]);if(1===t.length)return b(e,i,r);var a=[...e],t=t.slice(1),s=t[0];let c=e[i];return null==c&&(c=void 0!==s?f(s):{}),a[i]=p(c,t,r),a},o=(e,t,r)=>{var i=t[0];if(void 0===i)return e;if(1===t.length)return d(e,i,r);var t=t.slice(1),a=t[0];let s=e[i];null==s&&(s=void 0!==a?f(a):{});a={...e};return a[i]=p(s,t,r),a},p=(e,t,r)=>0===t.length?r:null==e?p({},t,r):void 0===t[0]?e:(Array.isArray(e)?S:o)(e,t,r);export{a as state,e as effect,t as batch,r as derive,s as select,c as readonlyState,n as protectedState,u as lens};
|
package/package.json
CHANGED
|
@@ -1,80 +1,97 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"version": "1000.2.1",
|
|
2
|
+
"author": "Denny Trebbin (nerdalytics)",
|
|
4
3
|
"description": "A lightweight reactive state library for Node.js backends. Enables reactive state management with automatic dependency tracking and efficient updates for server-side applications.",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
"devDependencies": {
|
|
5
|
+
"@biomejs/biome": "2.2.7",
|
|
6
|
+
"@types/node": "24.9.1",
|
|
7
|
+
"npm-check-updates": "19.1.1",
|
|
8
|
+
"typescript": "5.9.3",
|
|
9
|
+
"uglify-js": "3.19.3"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=20.0.0"
|
|
13
|
+
},
|
|
9
14
|
"exports": {
|
|
10
15
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
16
|
+
"import": {
|
|
17
|
+
"default": "./dist/src/index.min.js",
|
|
18
|
+
"types": "./dist/src/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"default": "./dist/src/index.min.js"
|
|
22
|
+
},
|
|
23
|
+
"types": "./dist/src/index.d.ts",
|
|
24
|
+
"typescript": "./src/index.ts"
|
|
13
25
|
}
|
|
14
26
|
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/src/index.min.js",
|
|
29
|
+
"dist/src/index.d.ts",
|
|
30
|
+
"src/index.ts",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [
|
|
34
|
+
"backend",
|
|
35
|
+
"batching",
|
|
36
|
+
"computed-values",
|
|
37
|
+
"dependency-tracking",
|
|
38
|
+
"effects",
|
|
39
|
+
"fine-grained",
|
|
40
|
+
"lightweight",
|
|
41
|
+
"memory-management",
|
|
42
|
+
"nodejs",
|
|
43
|
+
"performance",
|
|
44
|
+
"reactive",
|
|
45
|
+
"server-side",
|
|
46
|
+
"signals",
|
|
47
|
+
"state-management",
|
|
48
|
+
"typescript"
|
|
49
|
+
],
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"main": "dist/src/index.min.js",
|
|
52
|
+
"name": "@nerdalytics/beacon",
|
|
53
|
+
"packageManager": "npm@11.6.2",
|
|
15
54
|
"repository": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/nerdalytics/beacon.git"
|
|
18
57
|
},
|
|
19
58
|
"scripts": {
|
|
20
|
-
"lint": "npx @biomejs/biome lint --config-path=./biome.json",
|
|
21
|
-
"lint:fix": "npx @biomejs/biome lint --fix --config-path=./biome.json",
|
|
22
|
-
"lint:fix:unsafe": "npx @biomejs/biome lint --fix --unsafe --config-path=./biome.json",
|
|
23
|
-
"format": "npx @biomejs/biome format --write --config-path=./biome.json",
|
|
24
|
-
"check": "npx @biomejs/biome check --config-path=./biome.json",
|
|
25
|
-
"check:fix": "npx @biomejs/biome format --fix --config-path=./biome.json",
|
|
26
|
-
"test": "node --test --test-skip-pattern=\"COMPONENT NAME\" tests/**/*.ts",
|
|
27
|
-
"test:coverage": "node --test --experimental-config-file=node.config.json --test-skip-pattern=\"[COMPONENT NAME]\" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info tests/**/*.ts",
|
|
28
|
-
"test:unit:state": "node --test tests/state.test.ts",
|
|
29
|
-
"test:unit:effect": "node --test tests/effect.test.ts",
|
|
30
|
-
"test:unit:batch": "node --test tests/batch.test.ts",
|
|
31
|
-
"test:unit:derive": "node --test tests/derive.test.ts",
|
|
32
|
-
"test:unit:select": "node --test tests/select.test.ts",
|
|
33
|
-
"test:unit:lens": "node --test tests/lens.test.ts",
|
|
34
|
-
"test:unit:cleanup": "node --test tests/cleanup.test.ts",
|
|
35
|
-
"test:unit:cyclic-dependency": "node --test tests/cyclic-dependency.test.ts",
|
|
36
|
-
"test:unit:deep-chain": "node --test tests/deep-chain.test.ts",
|
|
37
|
-
"test:unit:infinite-loop": "node --test tests/infinite-loop.test.ts",
|
|
38
|
-
"test:unit:custom-equality": "node --test tests/custom-equality.test.ts",
|
|
39
59
|
"benchmark": "node scripts/benchmark.ts",
|
|
60
|
+
"benchmark:naiv": "node scripts/naiv-benchmark.ts",
|
|
40
61
|
"build": "npm run build:lts",
|
|
41
|
-
"prebuild:lts": "rm -rf dist/",
|
|
42
62
|
"build:lts": "tsc -p tsconfig.lts.json",
|
|
63
|
+
"check": "npx @biomejs/biome check",
|
|
64
|
+
"check:fix": "npx @biomejs/biome format --fix",
|
|
65
|
+
"format": "npx @biomejs/biome format --write",
|
|
66
|
+
"lint": "npx @biomejs/biome lint",
|
|
67
|
+
"lint:fix": "npx @biomejs/biome lint --fix",
|
|
68
|
+
"lint:fix:unsafe": "npx @biomejs/biome lint --fix --unsafe",
|
|
43
69
|
"postbuild:lts": "npx uglify-js --compress --mangle --module --toplevel --v8 --warn --source-map \"content='dist/src/index.js.map'\" --output dist/src/index.min.js dist/src/index.js",
|
|
70
|
+
"prebuild:lts": "rm -rf dist/",
|
|
44
71
|
"prepublishOnly": "npm run build:lts",
|
|
45
72
|
"pretest:lts": "node scripts/run-lts-tests.js",
|
|
73
|
+
"test": "node --test --test-skip-pattern=\"COMPONENT NAME\" tests/**/*.ts",
|
|
74
|
+
"test:behavior": "node --test tests/infinite-loop.test.ts tests/cyclic-dependency.test.ts tests/cleanup.test.ts",
|
|
75
|
+
"test:core": "node --test tests/*-core.test.ts",
|
|
76
|
+
"test:coverage": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info tests/**/*.test.ts",
|
|
77
|
+
"test:integration": "node --test tests/state-*.test.ts tests/batch-integration.test.ts",
|
|
46
78
|
"test:lts:20": "node --test dist/tests/**.js",
|
|
47
79
|
"test:lts:22": "node --test --test-skip-pattern=\"COMPONENT NAME\" dist/tests/**/*.js",
|
|
80
|
+
"test:unit:batch": "node --test tests/batch.test.ts",
|
|
81
|
+
"test:unit:cleanup": "node --test tests/cleanup.test.ts",
|
|
82
|
+
"test:unit:custom-equality": "node --test tests/custom-equality.test.ts",
|
|
83
|
+
"test:unit:cyclic-dependency": "node --test tests/cyclic-dependency.test.ts",
|
|
84
|
+
"test:unit:deep-chain": "node --test tests/deep-chain.test.ts",
|
|
85
|
+
"test:unit:derive": "node --test tests/derive.test.ts",
|
|
86
|
+
"test:unit:effect": "node --test tests/effect.test.ts",
|
|
87
|
+
"test:unit:infinite-loop": "node --test tests/infinite-loop.test.ts",
|
|
88
|
+
"test:unit:lens": "node --test tests/lens.test.ts",
|
|
89
|
+
"test:unit:select": "node --test tests/select.test.ts",
|
|
90
|
+
"test:unit:state": "node --test tests/state.test.ts",
|
|
91
|
+
"update-dependencies": "npx npm-check-updates --interactive --upgrade --removeRange",
|
|
48
92
|
"update-performance-docs": "node --experimental-config-file=node.config.json scripts/update-performance-docs.ts"
|
|
49
93
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"fine-grained",
|
|
54
|
-
"computed-values",
|
|
55
|
-
"batching",
|
|
56
|
-
"signals",
|
|
57
|
-
"reactive",
|
|
58
|
-
"lightweight",
|
|
59
|
-
"performance",
|
|
60
|
-
"dependency-tracking",
|
|
61
|
-
"memoization",
|
|
62
|
-
"memory-management",
|
|
63
|
-
"nodejs",
|
|
64
|
-
"server-side",
|
|
65
|
-
"backend",
|
|
66
|
-
"typescript"
|
|
67
|
-
],
|
|
68
|
-
"author": "Denny Trebbin (nerdalytics)",
|
|
69
|
-
"license": "MIT",
|
|
70
|
-
"devDependencies": {
|
|
71
|
-
"@biomejs/biome": "1.9.4",
|
|
72
|
-
"@types/node": "22.14.1",
|
|
73
|
-
"typescript": "5.8.3",
|
|
74
|
-
"uglify-js": "3.19.3"
|
|
75
|
-
},
|
|
76
|
-
"engines": {
|
|
77
|
-
"node": ">=20.0.0"
|
|
78
|
-
},
|
|
79
|
-
"packageManager": "npm@11.3.0"
|
|
94
|
+
"type": "module",
|
|
95
|
+
"types": "dist/src/index.d.ts",
|
|
96
|
+
"version": "1000.2.3"
|
|
80
97
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Core types for reactive primitives
|
|
2
2
|
type Subscriber = () => void
|
|
3
|
-
type Unsubscribe = () => void
|
|
3
|
+
export type Unsubscribe = () => void
|
|
4
4
|
export type ReadOnlyState<T> = () => T
|
|
5
5
|
export interface WriteableState<T> {
|
|
6
6
|
set(value: T): void
|
|
@@ -8,7 +8,7 @@ export interface WriteableState<T> {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
// Special symbol used for internal tracking
|
|
11
|
-
const STATE_ID = Symbol()
|
|
11
|
+
const STATE_ID: unique symbol = Symbol('STATE_ID')
|
|
12
12
|
|
|
13
13
|
export type State<T> = ReadOnlyState<T> &
|
|
14
14
|
WriteableState<T> & {
|
|
@@ -59,7 +59,10 @@ export const readonlyState =
|
|
|
59
59
|
export const protectedState = <T>(
|
|
60
60
|
initialValue: T,
|
|
61
61
|
equalityFn: (a: T, b: T) => boolean = Object.is
|
|
62
|
-
): [
|
|
62
|
+
): [
|
|
63
|
+
ReadOnlyState<T>,
|
|
64
|
+
WriteableState<T>,
|
|
65
|
+
] => {
|
|
63
66
|
const fullState = state(initialValue, equalityFn)
|
|
64
67
|
return [
|
|
65
68
|
(): T => readonlyState(fullState)(),
|
|
@@ -307,7 +310,9 @@ class StateImpl<T> {
|
|
|
307
310
|
if (StateImpl.batchDepth === 0) {
|
|
308
311
|
// Process effects created during the batch
|
|
309
312
|
if (StateImpl.deferredEffectCreations.length > 0) {
|
|
310
|
-
const effectsToRun = [
|
|
313
|
+
const effectsToRun = [
|
|
314
|
+
...StateImpl.deferredEffectCreations,
|
|
315
|
+
]
|
|
311
316
|
StateImpl.deferredEffectCreations.length = 0
|
|
312
317
|
for (const effect of effectsToRun) {
|
|
313
318
|
effect()
|
|
@@ -327,33 +332,37 @@ class StateImpl<T> {
|
|
|
327
332
|
* Implementation of the public 'derive' function.
|
|
328
333
|
*/
|
|
329
334
|
static createDerive = <T>(computeFn: () => T): ReadOnlyState<T> => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
335
|
+
// Create a container to hold state and minimize closure captures
|
|
336
|
+
const container = {
|
|
337
|
+
cachedValue: undefined as unknown as T,
|
|
338
|
+
computeFn,
|
|
339
|
+
initialized: false,
|
|
340
|
+
valueState: StateImpl.createState<T | undefined>(undefined),
|
|
341
|
+
}
|
|
333
342
|
|
|
334
343
|
// Internal effect automatically tracks dependencies and updates the derived value
|
|
335
|
-
StateImpl.createEffect((): void
|
|
336
|
-
const newValue = computeFn()
|
|
344
|
+
StateImpl.createEffect(function deriveEffect(): void {
|
|
345
|
+
const newValue = container.computeFn()
|
|
337
346
|
|
|
338
347
|
// Only update if the value actually changed to preserve referential equality
|
|
339
348
|
// and prevent unnecessary downstream updates
|
|
340
|
-
if (!(initialized && Object.is(cachedValue, newValue))) {
|
|
341
|
-
cachedValue = newValue
|
|
342
|
-
valueState.set(newValue)
|
|
349
|
+
if (!(container.initialized && Object.is(container.cachedValue, newValue))) {
|
|
350
|
+
container.cachedValue = newValue
|
|
351
|
+
container.valueState.set(newValue)
|
|
343
352
|
}
|
|
344
353
|
|
|
345
|
-
initialized = true
|
|
354
|
+
container.initialized = true
|
|
346
355
|
})
|
|
347
356
|
|
|
348
357
|
// Return function with lazy initialization - ensures value is available
|
|
349
358
|
// even when accessed before its dependencies have had a chance to update
|
|
350
|
-
return (): T
|
|
351
|
-
if (!initialized) {
|
|
352
|
-
cachedValue = computeFn()
|
|
353
|
-
initialized = true
|
|
354
|
-
valueState.set(cachedValue)
|
|
359
|
+
return function deriveGetter(): T {
|
|
360
|
+
if (!container.initialized) {
|
|
361
|
+
container.cachedValue = container.computeFn()
|
|
362
|
+
container.initialized = true
|
|
363
|
+
container.valueState.set(container.cachedValue)
|
|
355
364
|
}
|
|
356
|
-
return valueState() as T
|
|
365
|
+
return container.valueState() as T
|
|
357
366
|
}
|
|
358
367
|
}
|
|
359
368
|
|
|
@@ -366,44 +375,54 @@ class StateImpl<T> {
|
|
|
366
375
|
selectorFn: (state: T) => R,
|
|
367
376
|
equalityFn: (a: R, b: R) => boolean = Object.is
|
|
368
377
|
): ReadOnlyState<R> => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
378
|
+
// Create a container to hold state and minimize closure captures
|
|
379
|
+
const container = {
|
|
380
|
+
equalityFn,
|
|
381
|
+
initialized: false,
|
|
382
|
+
lastSelectedValue: undefined as R | undefined,
|
|
383
|
+
lastSourceValue: undefined as T | undefined,
|
|
384
|
+
selectorFn,
|
|
385
|
+
source,
|
|
386
|
+
valueState: StateImpl.createState<R | undefined>(undefined),
|
|
387
|
+
}
|
|
373
388
|
|
|
374
389
|
// Internal effect to track the source and update only when needed
|
|
375
|
-
StateImpl.createEffect((): void
|
|
376
|
-
const sourceValue = source()
|
|
390
|
+
StateImpl.createEffect(function selectEffect(): void {
|
|
391
|
+
const sourceValue = container.source()
|
|
377
392
|
|
|
378
393
|
// Skip computation if source reference hasn't changed
|
|
379
|
-
if (initialized && Object.is(lastSourceValue, sourceValue)) {
|
|
394
|
+
if (container.initialized && Object.is(container.lastSourceValue, sourceValue)) {
|
|
380
395
|
return
|
|
381
396
|
}
|
|
382
397
|
|
|
383
|
-
lastSourceValue = sourceValue
|
|
384
|
-
const newSelectedValue = selectorFn(sourceValue)
|
|
398
|
+
container.lastSourceValue = sourceValue
|
|
399
|
+
const newSelectedValue = container.selectorFn(sourceValue)
|
|
385
400
|
|
|
386
401
|
// Use custom equality function to determine if value semantically changed,
|
|
387
402
|
// allowing for deep equality comparisons with complex objects
|
|
388
|
-
if (
|
|
403
|
+
if (
|
|
404
|
+
container.initialized &&
|
|
405
|
+
container.lastSelectedValue !== undefined &&
|
|
406
|
+
container.equalityFn(container.lastSelectedValue, newSelectedValue)
|
|
407
|
+
) {
|
|
389
408
|
return
|
|
390
409
|
}
|
|
391
410
|
|
|
392
411
|
// Update cache and notify subscribers due the value has changed
|
|
393
|
-
lastSelectedValue = newSelectedValue
|
|
394
|
-
valueState.set(newSelectedValue)
|
|
395
|
-
initialized = true
|
|
412
|
+
container.lastSelectedValue = newSelectedValue
|
|
413
|
+
container.valueState.set(newSelectedValue)
|
|
414
|
+
container.initialized = true
|
|
396
415
|
})
|
|
397
416
|
|
|
398
417
|
// Return function with eager initialization capability
|
|
399
|
-
return (): R
|
|
400
|
-
if (!initialized) {
|
|
401
|
-
lastSourceValue = source()
|
|
402
|
-
lastSelectedValue = selectorFn(lastSourceValue)
|
|
403
|
-
valueState.set(lastSelectedValue)
|
|
404
|
-
initialized = true
|
|
418
|
+
return function selectGetter(): R {
|
|
419
|
+
if (!container.initialized) {
|
|
420
|
+
container.lastSourceValue = container.source()
|
|
421
|
+
container.lastSelectedValue = container.selectorFn(container.lastSourceValue)
|
|
422
|
+
container.valueState.set(container.lastSelectedValue)
|
|
423
|
+
container.initialized = true
|
|
405
424
|
}
|
|
406
|
-
return valueState() as R
|
|
425
|
+
return container.valueState() as R
|
|
407
426
|
}
|
|
408
427
|
}
|
|
409
428
|
|
|
@@ -412,15 +431,25 @@ class StateImpl<T> {
|
|
|
412
431
|
* Implementation of the public 'lens' function.
|
|
413
432
|
*/
|
|
414
433
|
static createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> => {
|
|
434
|
+
// Create a container to hold lens state and minimize closure captures
|
|
435
|
+
const container = {
|
|
436
|
+
accessor,
|
|
437
|
+
isUpdating: false,
|
|
438
|
+
lensState: null as unknown as State<K>,
|
|
439
|
+
originalSet: null as unknown as (value: K) => void,
|
|
440
|
+
path: [] as (string | number)[],
|
|
441
|
+
source,
|
|
442
|
+
}
|
|
443
|
+
|
|
415
444
|
// Extract the property path once during lens creation
|
|
416
445
|
const extractPath = (): (string | number)[] => {
|
|
417
|
-
const
|
|
446
|
+
const pathCollector: (string | number)[] = []
|
|
418
447
|
const proxy = new Proxy(
|
|
419
448
|
{},
|
|
420
449
|
{
|
|
421
450
|
get: (_: object, prop: string | symbol): unknown => {
|
|
422
451
|
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
423
|
-
|
|
452
|
+
pathCollector.push(prop)
|
|
424
453
|
}
|
|
425
454
|
return proxy
|
|
426
455
|
},
|
|
@@ -428,62 +457,59 @@ class StateImpl<T> {
|
|
|
428
457
|
)
|
|
429
458
|
|
|
430
459
|
try {
|
|
431
|
-
accessor(proxy as unknown as T)
|
|
460
|
+
container.accessor(proxy as unknown as T)
|
|
432
461
|
} catch {
|
|
433
462
|
// Ignore errors, we're just collecting the path
|
|
434
463
|
}
|
|
435
464
|
|
|
436
|
-
return
|
|
465
|
+
return pathCollector
|
|
437
466
|
}
|
|
438
467
|
|
|
439
468
|
// Capture the path once
|
|
440
|
-
|
|
469
|
+
container.path = extractPath()
|
|
441
470
|
|
|
442
471
|
// Create a state with the initial value from the source
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// Prevent circular updates
|
|
446
|
-
let isUpdating = false
|
|
472
|
+
container.lensState = StateImpl.createState<K>(container.accessor(container.source()))
|
|
473
|
+
container.originalSet = container.lensState.set
|
|
447
474
|
|
|
448
475
|
// Set up an effect to sync from source to lens
|
|
449
|
-
StateImpl.createEffect((): void
|
|
450
|
-
if (isUpdating) {
|
|
476
|
+
StateImpl.createEffect(function lensEffect(): void {
|
|
477
|
+
if (container.isUpdating) {
|
|
451
478
|
return
|
|
452
479
|
}
|
|
453
480
|
|
|
454
|
-
isUpdating = true
|
|
481
|
+
container.isUpdating = true
|
|
455
482
|
try {
|
|
456
|
-
lensState.set(accessor(source()))
|
|
483
|
+
container.lensState.set(container.accessor(container.source()))
|
|
457
484
|
} finally {
|
|
458
|
-
isUpdating = false
|
|
485
|
+
container.isUpdating = false
|
|
459
486
|
}
|
|
460
487
|
})
|
|
461
488
|
|
|
462
489
|
// Override the lens state's set method to update the source
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (isUpdating) {
|
|
490
|
+
container.lensState.set = function lensSet(value: K): void {
|
|
491
|
+
if (container.isUpdating) {
|
|
466
492
|
return
|
|
467
493
|
}
|
|
468
494
|
|
|
469
|
-
isUpdating = true
|
|
495
|
+
container.isUpdating = true
|
|
470
496
|
try {
|
|
471
497
|
// Update lens state
|
|
472
|
-
originalSet(value)
|
|
498
|
+
container.originalSet(value)
|
|
473
499
|
|
|
474
500
|
// Update source by modifying the value at path
|
|
475
|
-
source.update((current: T): T => setValueAtPath(current, path, value))
|
|
501
|
+
container.source.update((current: T): T => setValueAtPath(current, container.path, value))
|
|
476
502
|
} finally {
|
|
477
|
-
isUpdating = false
|
|
503
|
+
container.isUpdating = false
|
|
478
504
|
}
|
|
479
505
|
}
|
|
480
506
|
|
|
481
507
|
// Add update method for completeness
|
|
482
|
-
lensState.update = (fn: (value: K) => K): void
|
|
483
|
-
lensState.set(fn(lensState()))
|
|
508
|
+
container.lensState.update = function lensUpdate(fn: (value: K) => K): void {
|
|
509
|
+
container.lensState.set(fn(container.lensState()))
|
|
484
510
|
}
|
|
485
511
|
|
|
486
|
-
return lensState
|
|
512
|
+
return container.lensState
|
|
487
513
|
}
|
|
488
514
|
|
|
489
515
|
// Processes queued subscriber notifications in a controlled, non-reentrant way
|
|
@@ -532,7 +558,9 @@ class StateImpl<T> {
|
|
|
532
558
|
}
|
|
533
559
|
// Helper for array updates
|
|
534
560
|
const updateArrayItem = <V>(arr: unknown[], index: number, value: V): unknown[] => {
|
|
535
|
-
const copy = [
|
|
561
|
+
const copy = [
|
|
562
|
+
...arr,
|
|
563
|
+
]
|
|
536
564
|
copy[index] = value
|
|
537
565
|
return copy
|
|
538
566
|
}
|
|
@@ -543,7 +571,9 @@ const updateShallowProperty = <V>(
|
|
|
543
571
|
key: string | number,
|
|
544
572
|
value: V
|
|
545
573
|
): Record<string | number, unknown> => {
|
|
546
|
-
const result = {
|
|
574
|
+
const result = {
|
|
575
|
+
...obj,
|
|
576
|
+
}
|
|
547
577
|
result[key] = value
|
|
548
578
|
return result
|
|
549
579
|
}
|
|
@@ -564,7 +594,9 @@ const updateArrayPath = <V>(array: unknown[], pathSegments: (string | number)[],
|
|
|
564
594
|
}
|
|
565
595
|
|
|
566
596
|
// Nested path in array
|
|
567
|
-
const copy = [
|
|
597
|
+
const copy = [
|
|
598
|
+
...array,
|
|
599
|
+
]
|
|
568
600
|
const nextPathSegments = pathSegments.slice(1)
|
|
569
601
|
const nextKey = nextPathSegments[0]
|
|
570
602
|
|
|
@@ -609,7 +641,9 @@ const updateObjectPath = <V>(
|
|
|
609
641
|
}
|
|
610
642
|
|
|
611
643
|
// Create new object with updated property
|
|
612
|
-
const result = {
|
|
644
|
+
const result = {
|
|
645
|
+
...obj,
|
|
646
|
+
}
|
|
613
647
|
result[currentKey] = setValueAtPath(currentValue, nextPathSegments, value)
|
|
614
648
|
return result
|
|
615
649
|
}
|
package/dist/src/index.js
DELETED
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
const STATE_ID = Symbol();
|
|
2
|
-
export const state = (initialValue, equalityFn = Object.is) => StateImpl.createState(initialValue, equalityFn);
|
|
3
|
-
export const effect = (fn) => StateImpl.createEffect(fn);
|
|
4
|
-
export const batch = (fn) => StateImpl.executeBatch(fn);
|
|
5
|
-
export const derive = (computeFn) => StateImpl.createDerive(computeFn);
|
|
6
|
-
export const select = (source, selectorFn, equalityFn = Object.is) => StateImpl.createSelect(source, selectorFn, equalityFn);
|
|
7
|
-
export const readonlyState = (state) => () => state();
|
|
8
|
-
export const protectedState = (initialValue, equalityFn = Object.is) => {
|
|
9
|
-
const fullState = state(initialValue, equalityFn);
|
|
10
|
-
return [
|
|
11
|
-
() => readonlyState(fullState)(),
|
|
12
|
-
{
|
|
13
|
-
set: (value) => fullState.set(value),
|
|
14
|
-
update: (fn) => fullState.update(fn),
|
|
15
|
-
},
|
|
16
|
-
];
|
|
17
|
-
};
|
|
18
|
-
export const lens = (source, accessor) => StateImpl.createLens(source, accessor);
|
|
19
|
-
class StateImpl {
|
|
20
|
-
static currentSubscriber = null;
|
|
21
|
-
static pendingSubscribers = new Set();
|
|
22
|
-
static isNotifying = false;
|
|
23
|
-
static batchDepth = 0;
|
|
24
|
-
static deferredEffectCreations = [];
|
|
25
|
-
static activeSubscribers = new Set();
|
|
26
|
-
static stateTracking = new WeakMap();
|
|
27
|
-
static subscriberDependencies = new WeakMap();
|
|
28
|
-
static parentSubscriber = new WeakMap();
|
|
29
|
-
static childSubscribers = new WeakMap();
|
|
30
|
-
value;
|
|
31
|
-
subscribers = new Set();
|
|
32
|
-
stateId = Symbol();
|
|
33
|
-
equalityFn;
|
|
34
|
-
constructor(initialValue, equalityFn = Object.is) {
|
|
35
|
-
this.value = initialValue;
|
|
36
|
-
this.equalityFn = equalityFn;
|
|
37
|
-
}
|
|
38
|
-
static createState = (initialValue, equalityFn = Object.is) => {
|
|
39
|
-
const instance = new StateImpl(initialValue, equalityFn);
|
|
40
|
-
const get = () => instance.get();
|
|
41
|
-
get.set = (value) => instance.set(value);
|
|
42
|
-
get.update = (fn) => instance.update(fn);
|
|
43
|
-
get[STATE_ID] = instance.stateId;
|
|
44
|
-
return get;
|
|
45
|
-
};
|
|
46
|
-
get = () => {
|
|
47
|
-
const currentEffect = StateImpl.currentSubscriber;
|
|
48
|
-
if (currentEffect) {
|
|
49
|
-
this.subscribers.add(currentEffect);
|
|
50
|
-
let dependencies = StateImpl.subscriberDependencies.get(currentEffect);
|
|
51
|
-
if (!dependencies) {
|
|
52
|
-
dependencies = new Set();
|
|
53
|
-
StateImpl.subscriberDependencies.set(currentEffect, dependencies);
|
|
54
|
-
}
|
|
55
|
-
dependencies.add(this.subscribers);
|
|
56
|
-
let readStates = StateImpl.stateTracking.get(currentEffect);
|
|
57
|
-
if (!readStates) {
|
|
58
|
-
readStates = new Set();
|
|
59
|
-
StateImpl.stateTracking.set(currentEffect, readStates);
|
|
60
|
-
}
|
|
61
|
-
readStates.add(this.stateId);
|
|
62
|
-
}
|
|
63
|
-
return this.value;
|
|
64
|
-
};
|
|
65
|
-
set = (newValue) => {
|
|
66
|
-
if (this.equalityFn(this.value, newValue)) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const effect = StateImpl.currentSubscriber;
|
|
70
|
-
if (effect) {
|
|
71
|
-
const states = StateImpl.stateTracking.get(effect);
|
|
72
|
-
if (states?.has(this.stateId) && !StateImpl.parentSubscriber.get(effect)) {
|
|
73
|
-
throw new Error('Infinite loop detected: effect() cannot update a state() it depends on!');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
this.value = newValue;
|
|
77
|
-
if (this.subscribers.size === 0) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
for (const sub of this.subscribers) {
|
|
81
|
-
StateImpl.pendingSubscribers.add(sub);
|
|
82
|
-
}
|
|
83
|
-
if (StateImpl.batchDepth === 0 && !StateImpl.isNotifying) {
|
|
84
|
-
StateImpl.notifySubscribers();
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
update = (fn) => {
|
|
88
|
-
this.set(fn(this.value));
|
|
89
|
-
};
|
|
90
|
-
static createEffect = (fn) => {
|
|
91
|
-
const runEffect = () => {
|
|
92
|
-
if (StateImpl.activeSubscribers.has(runEffect)) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
StateImpl.activeSubscribers.add(runEffect);
|
|
96
|
-
const parentEffect = StateImpl.currentSubscriber;
|
|
97
|
-
try {
|
|
98
|
-
StateImpl.cleanupEffect(runEffect);
|
|
99
|
-
StateImpl.currentSubscriber = runEffect;
|
|
100
|
-
StateImpl.stateTracking.set(runEffect, new Set());
|
|
101
|
-
if (parentEffect) {
|
|
102
|
-
StateImpl.parentSubscriber.set(runEffect, parentEffect);
|
|
103
|
-
let children = StateImpl.childSubscribers.get(parentEffect);
|
|
104
|
-
if (!children) {
|
|
105
|
-
children = new Set();
|
|
106
|
-
StateImpl.childSubscribers.set(parentEffect, children);
|
|
107
|
-
}
|
|
108
|
-
children.add(runEffect);
|
|
109
|
-
}
|
|
110
|
-
fn();
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
StateImpl.currentSubscriber = parentEffect;
|
|
114
|
-
StateImpl.activeSubscribers.delete(runEffect);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
if (StateImpl.batchDepth === 0) {
|
|
118
|
-
runEffect();
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
if (StateImpl.currentSubscriber) {
|
|
122
|
-
const parent = StateImpl.currentSubscriber;
|
|
123
|
-
StateImpl.parentSubscriber.set(runEffect, parent);
|
|
124
|
-
let children = StateImpl.childSubscribers.get(parent);
|
|
125
|
-
if (!children) {
|
|
126
|
-
children = new Set();
|
|
127
|
-
StateImpl.childSubscribers.set(parent, children);
|
|
128
|
-
}
|
|
129
|
-
children.add(runEffect);
|
|
130
|
-
}
|
|
131
|
-
StateImpl.deferredEffectCreations.push(runEffect);
|
|
132
|
-
}
|
|
133
|
-
return () => {
|
|
134
|
-
StateImpl.cleanupEffect(runEffect);
|
|
135
|
-
StateImpl.pendingSubscribers.delete(runEffect);
|
|
136
|
-
StateImpl.activeSubscribers.delete(runEffect);
|
|
137
|
-
StateImpl.stateTracking.delete(runEffect);
|
|
138
|
-
const parent = StateImpl.parentSubscriber.get(runEffect);
|
|
139
|
-
if (parent) {
|
|
140
|
-
const siblings = StateImpl.childSubscribers.get(parent);
|
|
141
|
-
if (siblings) {
|
|
142
|
-
siblings.delete(runEffect);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
StateImpl.parentSubscriber.delete(runEffect);
|
|
146
|
-
const children = StateImpl.childSubscribers.get(runEffect);
|
|
147
|
-
if (children) {
|
|
148
|
-
for (const child of children) {
|
|
149
|
-
StateImpl.cleanupEffect(child);
|
|
150
|
-
}
|
|
151
|
-
children.clear();
|
|
152
|
-
StateImpl.childSubscribers.delete(runEffect);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
static executeBatch = (fn) => {
|
|
157
|
-
StateImpl.batchDepth++;
|
|
158
|
-
try {
|
|
159
|
-
return fn();
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
if (StateImpl.batchDepth === 1) {
|
|
163
|
-
StateImpl.pendingSubscribers.clear();
|
|
164
|
-
StateImpl.deferredEffectCreations.length = 0;
|
|
165
|
-
}
|
|
166
|
-
throw error;
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
StateImpl.batchDepth--;
|
|
170
|
-
if (StateImpl.batchDepth === 0) {
|
|
171
|
-
if (StateImpl.deferredEffectCreations.length > 0) {
|
|
172
|
-
const effectsToRun = [...StateImpl.deferredEffectCreations];
|
|
173
|
-
StateImpl.deferredEffectCreations.length = 0;
|
|
174
|
-
for (const effect of effectsToRun) {
|
|
175
|
-
effect();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (StateImpl.pendingSubscribers.size > 0 && !StateImpl.isNotifying) {
|
|
179
|
-
StateImpl.notifySubscribers();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
static createDerive = (computeFn) => {
|
|
185
|
-
const valueState = StateImpl.createState(undefined);
|
|
186
|
-
let initialized = false;
|
|
187
|
-
let cachedValue;
|
|
188
|
-
StateImpl.createEffect(() => {
|
|
189
|
-
const newValue = computeFn();
|
|
190
|
-
if (!(initialized && Object.is(cachedValue, newValue))) {
|
|
191
|
-
cachedValue = newValue;
|
|
192
|
-
valueState.set(newValue);
|
|
193
|
-
}
|
|
194
|
-
initialized = true;
|
|
195
|
-
});
|
|
196
|
-
return () => {
|
|
197
|
-
if (!initialized) {
|
|
198
|
-
cachedValue = computeFn();
|
|
199
|
-
initialized = true;
|
|
200
|
-
valueState.set(cachedValue);
|
|
201
|
-
}
|
|
202
|
-
return valueState();
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
static createSelect = (source, selectorFn, equalityFn = Object.is) => {
|
|
206
|
-
let lastSourceValue;
|
|
207
|
-
let lastSelectedValue;
|
|
208
|
-
let initialized = false;
|
|
209
|
-
const valueState = StateImpl.createState(undefined);
|
|
210
|
-
StateImpl.createEffect(() => {
|
|
211
|
-
const sourceValue = source();
|
|
212
|
-
if (initialized && Object.is(lastSourceValue, sourceValue)) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
lastSourceValue = sourceValue;
|
|
216
|
-
const newSelectedValue = selectorFn(sourceValue);
|
|
217
|
-
if (initialized && lastSelectedValue !== undefined && equalityFn(lastSelectedValue, newSelectedValue)) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
lastSelectedValue = newSelectedValue;
|
|
221
|
-
valueState.set(newSelectedValue);
|
|
222
|
-
initialized = true;
|
|
223
|
-
});
|
|
224
|
-
return () => {
|
|
225
|
-
if (!initialized) {
|
|
226
|
-
lastSourceValue = source();
|
|
227
|
-
lastSelectedValue = selectorFn(lastSourceValue);
|
|
228
|
-
valueState.set(lastSelectedValue);
|
|
229
|
-
initialized = true;
|
|
230
|
-
}
|
|
231
|
-
return valueState();
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
static createLens = (source, accessor) => {
|
|
235
|
-
const extractPath = () => {
|
|
236
|
-
const path = [];
|
|
237
|
-
const proxy = new Proxy({}, {
|
|
238
|
-
get: (_, prop) => {
|
|
239
|
-
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
240
|
-
path.push(prop);
|
|
241
|
-
}
|
|
242
|
-
return proxy;
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
try {
|
|
246
|
-
accessor(proxy);
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
}
|
|
250
|
-
return path;
|
|
251
|
-
};
|
|
252
|
-
const path = extractPath();
|
|
253
|
-
const lensState = StateImpl.createState(accessor(source()));
|
|
254
|
-
let isUpdating = false;
|
|
255
|
-
StateImpl.createEffect(() => {
|
|
256
|
-
if (isUpdating) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
isUpdating = true;
|
|
260
|
-
try {
|
|
261
|
-
lensState.set(accessor(source()));
|
|
262
|
-
}
|
|
263
|
-
finally {
|
|
264
|
-
isUpdating = false;
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
const originalSet = lensState.set;
|
|
268
|
-
lensState.set = (value) => {
|
|
269
|
-
if (isUpdating) {
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
isUpdating = true;
|
|
273
|
-
try {
|
|
274
|
-
originalSet(value);
|
|
275
|
-
source.update((current) => setValueAtPath(current, path, value));
|
|
276
|
-
}
|
|
277
|
-
finally {
|
|
278
|
-
isUpdating = false;
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
lensState.update = (fn) => {
|
|
282
|
-
lensState.set(fn(lensState()));
|
|
283
|
-
};
|
|
284
|
-
return lensState;
|
|
285
|
-
};
|
|
286
|
-
static notifySubscribers = () => {
|
|
287
|
-
if (StateImpl.isNotifying) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
StateImpl.isNotifying = true;
|
|
291
|
-
try {
|
|
292
|
-
while (StateImpl.pendingSubscribers.size > 0) {
|
|
293
|
-
const subscribers = Array.from(StateImpl.pendingSubscribers);
|
|
294
|
-
StateImpl.pendingSubscribers.clear();
|
|
295
|
-
for (const effect of subscribers) {
|
|
296
|
-
effect();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
finally {
|
|
301
|
-
StateImpl.isNotifying = false;
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
static cleanupEffect = (effect) => {
|
|
305
|
-
StateImpl.pendingSubscribers.delete(effect);
|
|
306
|
-
const deps = StateImpl.subscriberDependencies.get(effect);
|
|
307
|
-
if (deps) {
|
|
308
|
-
for (const subscribers of deps) {
|
|
309
|
-
subscribers.delete(effect);
|
|
310
|
-
}
|
|
311
|
-
deps.clear();
|
|
312
|
-
StateImpl.subscriberDependencies.delete(effect);
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const updateArrayItem = (arr, index, value) => {
|
|
317
|
-
const copy = [...arr];
|
|
318
|
-
copy[index] = value;
|
|
319
|
-
return copy;
|
|
320
|
-
};
|
|
321
|
-
const updateShallowProperty = (obj, key, value) => {
|
|
322
|
-
const result = { ...obj };
|
|
323
|
-
result[key] = value;
|
|
324
|
-
return result;
|
|
325
|
-
};
|
|
326
|
-
const createContainer = (key) => {
|
|
327
|
-
const isArrayKey = typeof key === 'number' || !Number.isNaN(Number(key));
|
|
328
|
-
return isArrayKey ? [] : {};
|
|
329
|
-
};
|
|
330
|
-
const updateArrayPath = (array, pathSegments, value) => {
|
|
331
|
-
const index = Number(pathSegments[0]);
|
|
332
|
-
if (pathSegments.length === 1) {
|
|
333
|
-
return updateArrayItem(array, index, value);
|
|
334
|
-
}
|
|
335
|
-
const copy = [...array];
|
|
336
|
-
const nextPathSegments = pathSegments.slice(1);
|
|
337
|
-
const nextKey = nextPathSegments[0];
|
|
338
|
-
let nextValue = array[index];
|
|
339
|
-
if (nextValue === undefined || nextValue === null) {
|
|
340
|
-
nextValue = nextKey !== undefined ? createContainer(nextKey) : {};
|
|
341
|
-
}
|
|
342
|
-
copy[index] = setValueAtPath(nextValue, nextPathSegments, value);
|
|
343
|
-
return copy;
|
|
344
|
-
};
|
|
345
|
-
const updateObjectPath = (obj, pathSegments, value) => {
|
|
346
|
-
const currentKey = pathSegments[0];
|
|
347
|
-
if (currentKey === undefined) {
|
|
348
|
-
return obj;
|
|
349
|
-
}
|
|
350
|
-
if (pathSegments.length === 1) {
|
|
351
|
-
return updateShallowProperty(obj, currentKey, value);
|
|
352
|
-
}
|
|
353
|
-
const nextPathSegments = pathSegments.slice(1);
|
|
354
|
-
const nextKey = nextPathSegments[0];
|
|
355
|
-
let currentValue = obj[currentKey];
|
|
356
|
-
if (currentValue === undefined || currentValue === null) {
|
|
357
|
-
currentValue = nextKey !== undefined ? createContainer(nextKey) : {};
|
|
358
|
-
}
|
|
359
|
-
const result = { ...obj };
|
|
360
|
-
result[currentKey] = setValueAtPath(currentValue, nextPathSegments, value);
|
|
361
|
-
return result;
|
|
362
|
-
};
|
|
363
|
-
const setValueAtPath = (obj, pathSegments, value) => {
|
|
364
|
-
if (pathSegments.length === 0) {
|
|
365
|
-
return value;
|
|
366
|
-
}
|
|
367
|
-
if (obj === undefined || obj === null) {
|
|
368
|
-
return setValueAtPath({}, pathSegments, value);
|
|
369
|
-
}
|
|
370
|
-
const currentKey = pathSegments[0];
|
|
371
|
-
if (currentKey === undefined) {
|
|
372
|
-
return obj;
|
|
373
|
-
}
|
|
374
|
-
if (Array.isArray(obj)) {
|
|
375
|
-
return updateArrayPath(obj, pathSegments, value);
|
|
376
|
-
}
|
|
377
|
-
return updateObjectPath(obj, pathSegments, value);
|
|
378
|
-
};
|
|
379
|
-
//# sourceMappingURL=index.js.map
|