@ersbeth/picoflow 1.1.2 → 2.0.0
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/.vscode/settings.json +3 -3
- package/CHANGELOG.md +43 -0
- package/README.md +2 -18
- package/biome.json +45 -35
- package/dist/picoflow.js +856 -1530
- package/dist/types/api/base/flowDisposable.d.ts +41 -0
- package/dist/types/api/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/api/base/flowObservable.d.ts +27 -0
- package/dist/types/api/base/flowObservable.d.ts.map +1 -0
- package/dist/types/api/base/flowSubscribable.d.ts +79 -0
- package/dist/types/api/base/flowSubscribable.d.ts.map +1 -0
- package/dist/types/api/base/flowTracker.d.ts +8 -0
- package/dist/types/api/base/flowTracker.d.ts.map +1 -0
- package/dist/types/api/base/index.d.ts +5 -0
- package/dist/types/api/base/index.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +3 -0
- package/dist/types/api/index.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts +31 -0
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts +37 -0
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowStateAsync.d.ts +41 -0
- package/dist/types/api/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts +30 -0
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts.map +1 -0
- package/dist/types/{flow → api}/nodes/async/index.d.ts +1 -2
- package/dist/types/api/nodes/async/index.d.ts.map +1 -0
- package/dist/types/api/nodes/collections/flowArray.d.ts +134 -0
- package/dist/types/api/nodes/collections/flowArray.d.ts.map +1 -0
- package/dist/types/api/nodes/collections/flowMap.d.ts +98 -0
- package/dist/types/api/nodes/collections/flowMap.d.ts.map +1 -0
- package/dist/types/api/nodes/collections/index.d.ts.map +1 -0
- package/dist/types/api/nodes/flowEffect.d.ts +28 -0
- package/dist/types/api/nodes/flowEffect.d.ts.map +1 -0
- package/dist/types/api/nodes/flowSignal.d.ts +25 -0
- package/dist/types/api/nodes/flowSignal.d.ts.map +1 -0
- package/dist/types/api/nodes/flowValue.d.ts +35 -0
- package/dist/types/api/nodes/flowValue.d.ts.map +1 -0
- package/dist/types/api/nodes/index.d.ts +8 -0
- package/dist/types/api/nodes/index.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowConstant.d.ts +29 -0
- package/dist/types/api/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowDerivation.d.ts +36 -0
- package/dist/types/api/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowState.d.ts +39 -0
- package/dist/types/api/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts +28 -0
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts.map +1 -0
- package/dist/types/{flow → api}/nodes/sync/index.d.ts +1 -2
- package/dist/types/api/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/api/nodes/utils.d.ts +22 -0
- package/dist/types/api/nodes/utils.d.ts.map +1 -0
- package/dist/types/base/disposable.d.ts +11 -0
- package/dist/types/base/disposable.d.ts.map +1 -0
- package/dist/types/base/executionStack.d.ts +14 -0
- package/dist/types/base/executionStack.d.ts.map +1 -0
- package/dist/types/base/index.d.ts +6 -0
- package/dist/types/base/index.d.ts.map +1 -0
- package/dist/types/base/node.d.ts +27 -0
- package/dist/types/base/node.d.ts.map +1 -0
- package/dist/types/base/observable.d.ts +37 -0
- package/dist/types/base/observable.d.ts.map +1 -0
- package/dist/types/base/observer.d.ts +25 -0
- package/dist/types/base/observer.d.ts.map +1 -0
- package/dist/types/converters/index.d.ts +2 -0
- package/dist/types/converters/index.d.ts.map +1 -0
- package/dist/types/converters/solid.d.ts +46 -0
- package/dist/types/converters/solid.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -63
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/nodes/arrayNode.d.ts +2 -0
- package/dist/types/nodes/arrayNode.d.ts.map +1 -0
- package/dist/types/nodes/effectNode.d.ts +2 -0
- package/dist/types/nodes/effectNode.d.ts.map +1 -0
- package/dist/types/nodes/index.d.ts +9 -0
- package/dist/types/nodes/index.d.ts.map +1 -0
- package/dist/types/nodes/mapNode.d.ts +2 -0
- package/dist/types/nodes/mapNode.d.ts.map +1 -0
- package/dist/types/nodes/signalNode.d.ts +2 -0
- package/dist/types/nodes/signalNode.d.ts.map +1 -0
- package/dist/types/nodes/valueAsyncNode.d.ts +2 -0
- package/dist/types/nodes/valueAsyncNode.d.ts.map +1 -0
- package/dist/types/nodes/valueNode.d.ts +2 -0
- package/dist/types/nodes/valueNode.d.ts.map +1 -0
- package/dist/types/nodes/valueSyncNode.d.ts +2 -0
- package/dist/types/nodes/valueSyncNode.d.ts.map +1 -0
- package/dist/types/schedulers/asyncResolver.d.ts +2 -0
- package/dist/types/schedulers/asyncResolver.d.ts.map +1 -0
- package/dist/types/schedulers/asyncScheduler.d.ts +2 -0
- package/dist/types/schedulers/asyncScheduler.d.ts.map +1 -0
- package/dist/types/schedulers/index.d.ts +5 -0
- package/dist/types/schedulers/index.d.ts.map +1 -0
- package/dist/types/schedulers/pendingError.d.ts +2 -0
- package/dist/types/schedulers/pendingError.d.ts.map +1 -0
- package/dist/types/schedulers/scheduler.d.ts +2 -0
- package/dist/types/schedulers/scheduler.d.ts.map +1 -0
- package/dist/types/schedulers/syncResolver.d.ts +2 -0
- package/dist/types/schedulers/syncResolver.d.ts.map +1 -0
- package/dist/types/schedulers/syncScheduler.d.ts +2 -0
- package/dist/types/schedulers/syncScheduler.d.ts.map +1 -0
- package/docs/.vitepress/config.mts +128 -93
- package/docs/api/functions/array.md +14 -37
- package/docs/api/functions/constant.md +13 -25
- package/docs/api/functions/constantAsync.md +69 -0
- package/docs/api/functions/derivation.md +14 -33
- package/docs/api/functions/derivationAsync.md +34 -0
- package/docs/api/functions/from.md +62 -153
- package/docs/api/functions/isDisposable.md +8 -30
- package/docs/api/functions/map.md +15 -36
- package/docs/api/functions/signal.md +8 -23
- package/docs/api/functions/state.md +43 -23
- package/docs/api/functions/stateAsync.md +69 -0
- package/docs/api/functions/subscribe.md +40 -0
- package/docs/api/functions/writableDerivation.md +33 -0
- package/docs/api/functions/writableDerivationAsync.md +34 -0
- package/docs/api/index.md +45 -102
- package/docs/api/interfaces/FlowArray.md +439 -0
- package/docs/api/interfaces/FlowConstant.md +220 -0
- package/docs/api/interfaces/FlowConstantAsync.md +221 -0
- package/docs/api/interfaces/FlowDerivation.md +241 -0
- package/docs/api/interfaces/FlowDerivationAsync.md +242 -0
- package/docs/api/interfaces/FlowDisposable.md +32 -38
- package/docs/api/interfaces/FlowEffect.md +64 -0
- package/docs/api/interfaces/FlowMap.md +374 -0
- package/docs/api/interfaces/FlowObservable.md +155 -0
- package/docs/api/interfaces/FlowSignal.md +156 -0
- package/docs/api/interfaces/FlowState.md +269 -0
- package/docs/api/interfaces/FlowStateAsync.md +268 -0
- package/docs/api/interfaces/FlowSubscribable.md +55 -0
- package/docs/api/interfaces/FlowTracker.md +61 -0
- package/docs/api/interfaces/FlowValue.md +222 -0
- package/docs/api/interfaces/FlowWritableDerivation.md +292 -0
- package/docs/api/interfaces/FlowWritableDerivationAsync.md +293 -0
- package/docs/api/type-aliases/DerivationFunction.md +28 -0
- package/docs/api/type-aliases/DerivationFunctionAsync.md +28 -0
- package/docs/api/type-aliases/FlowArrayAction.md +19 -8
- package/docs/api/type-aliases/FlowDataTracker.md +33 -0
- package/docs/api/type-aliases/FlowMapAction.md +48 -0
- package/docs/api/type-aliases/FlowOnDataListener.md +33 -0
- package/docs/api/type-aliases/FlowOnErrorListener.md +27 -0
- package/docs/api/type-aliases/FlowOnPendingListener.md +21 -0
- package/docs/api/type-aliases/FlowReadonly.md +22 -0
- package/docs/api/type-aliases/InitFunction.md +21 -0
- package/docs/api/type-aliases/InitFunctionAsync.md +21 -0
- package/docs/api/type-aliases/NotPromise.md +6 -3
- package/docs/api/type-aliases/UpdateFunction.md +27 -0
- package/docs/api/type-aliases/UpdateFunctionAsync.md +27 -0
- package/docs/api/typedoc-sidebar.json +1 -81
- package/docs/examples/examples.md +0 -2
- package/docs/guide/advanced/architecture.md +1234 -0
- package/docs/guide/advanced/migration-v2.md +204 -0
- package/docs/guide/advanced/solidjs.md +2 -88
- package/docs/guide/introduction/concepts.md +4 -3
- package/docs/guide/introduction/conventions.md +2 -33
- package/docs/guide/introduction/getting-started.md +28 -23
- package/docs/guide/introduction/lifecycle.md +16 -19
- package/docs/guide/primitives/array.md +102 -216
- package/docs/guide/primitives/constant.md +39 -212
- package/docs/guide/primitives/derivations.md +55 -122
- package/docs/guide/primitives/effects.md +155 -241
- package/docs/guide/primitives/map.md +64 -186
- package/docs/guide/primitives/overview.md +45 -128
- package/docs/guide/primitives/signal.md +51 -88
- package/docs/guide/primitives/state.md +34 -130
- package/package.json +56 -60
- package/src/api/base/flowDisposable.ts +44 -0
- package/src/api/base/flowObservable.ts +28 -0
- package/src/api/base/flowSubscribable.ts +87 -0
- package/src/api/base/flowTracker.ts +7 -0
- package/src/api/base/index.ts +4 -0
- package/src/{flow → api}/index.ts +0 -1
- package/src/api/nodes/async/flowConstantAsync.ts +36 -0
- package/src/api/nodes/async/flowDerivationAsync.ts +42 -0
- package/src/api/nodes/async/flowStateAsync.ts +47 -0
- package/src/api/nodes/async/flowWritableDerivationAsync.ts +33 -0
- package/src/{flow → api}/nodes/async/index.ts +1 -2
- package/src/api/nodes/collections/flowArray.ts +155 -0
- package/src/api/nodes/collections/flowMap.ts +115 -0
- package/src/api/nodes/flowEffect.ts +42 -0
- package/src/api/nodes/flowSignal.ts +28 -0
- package/src/api/nodes/flowValue.ts +36 -0
- package/src/api/nodes/index.ts +7 -0
- package/src/api/nodes/sync/flowConstant.ts +33 -0
- package/src/api/nodes/sync/flowDerivation.ts +41 -0
- package/src/api/nodes/sync/flowState.ts +45 -0
- package/src/api/nodes/sync/flowWritableDerivation.ts +31 -0
- package/src/{flow → api}/nodes/sync/index.ts +1 -2
- package/src/api/nodes/utils.ts +22 -0
- package/src/base/disposable.ts +18 -0
- package/src/base/executionStack.ts +42 -0
- package/src/base/index.ts +5 -0
- package/src/base/node.ts +98 -0
- package/src/base/observable.ts +87 -0
- package/src/base/observer.ts +51 -0
- package/src/converters/index.ts +1 -0
- package/src/converters/solid.ts +109 -0
- package/src/index.ts +2 -64
- package/src/nodes/arrayNode.ts +172 -0
- package/src/nodes/effectNode.ts +59 -0
- package/src/nodes/index.ts +8 -0
- package/src/nodes/mapNode.ts +127 -0
- package/src/nodes/signalNode.ts +21 -0
- package/src/nodes/valueAsyncNode.ts +88 -0
- package/src/nodes/valueNode.ts +144 -0
- package/src/nodes/valueSyncNode.ts +128 -0
- package/src/schedulers/asyncResolver.ts +78 -0
- package/src/schedulers/asyncScheduler.ts +66 -0
- package/src/schedulers/index.ts +4 -0
- package/src/schedulers/pendingError.ts +13 -0
- package/src/schedulers/scheduler.ts +9 -0
- package/src/schedulers/syncResolver.ts +69 -0
- package/src/schedulers/syncScheduler.ts +55 -0
- package/test/base/pendingError.test.ts +67 -0
- package/test/converters/solid.derivation.browser.test.tsx +69 -0
- package/test/converters/solid.node.test.ts +654 -0
- package/test/converters/solid.state.browser.test.tsx +1592 -0
- package/test/reactivity/flowSignal.test.ts +226 -0
- package/test/reactivity/nodes/async/asyncScheduler/asyncResolver.test.ts +593 -0
- package/test/reactivity/nodes/async/asyncScheduler/asyncScheduler.test.ts +317 -0
- package/test/reactivity/nodes/async/flowConstantAsync.test.ts +652 -0
- package/test/reactivity/nodes/async/flowDerivation.test.ts +898 -0
- package/test/reactivity/nodes/async/flowDerivationAsync.test.ts +1716 -0
- package/test/reactivity/nodes/async/flowStateAsync.test.ts +708 -0
- package/test/reactivity/nodes/async/flowWritableDerivationAsync.test.ts +614 -0
- package/test/reactivity/nodes/collections/flowArray.asyncStates.test.ts +1289 -0
- package/test/reactivity/nodes/collections/flowArray.scalars.test.ts +961 -0
- package/test/reactivity/nodes/collections/flowArray.states.test.ts +1035 -0
- package/test/reactivity/nodes/collections/flowMap.asyncStates.test.ts +960 -0
- package/test/reactivity/nodes/collections/flowMap.scalars.test.ts +775 -0
- package/test/reactivity/nodes/collections/flowMap.states.test.ts +958 -0
- package/test/reactivity/nodes/sync/flowConstant.test.ts +377 -0
- package/test/reactivity/nodes/sync/flowDerivation.test.ts +896 -0
- package/test/reactivity/nodes/sync/flowState.test.ts +341 -0
- package/test/reactivity/nodes/sync/flowWritableDerivation.test.ts +603 -0
- package/test/vitest.d.ts +10 -0
- package/tsconfig.json +31 -20
- package/typedoc.json +35 -35
- package/vite.config.ts +25 -23
- package/vitest.browser.config.ts +21 -0
- package/vitest.config.ts +12 -12
- package/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +0 -372
- package/.cursor/plans/update-js-e795d61b.plan.md +0 -567
- package/dist/types/flow/base/flowDisposable.d.ts +0 -67
- package/dist/types/flow/base/flowDisposable.d.ts.map +0 -1
- package/dist/types/flow/base/flowEffect.d.ts +0 -127
- package/dist/types/flow/base/flowEffect.d.ts.map +0 -1
- package/dist/types/flow/base/flowGraph.d.ts +0 -97
- package/dist/types/flow/base/flowGraph.d.ts.map +0 -1
- package/dist/types/flow/base/flowSignal.d.ts +0 -134
- package/dist/types/flow/base/flowSignal.d.ts.map +0 -1
- package/dist/types/flow/base/flowTracker.d.ts +0 -15
- package/dist/types/flow/base/flowTracker.d.ts.map +0 -1
- package/dist/types/flow/base/index.d.ts +0 -7
- package/dist/types/flow/base/index.d.ts.map +0 -1
- package/dist/types/flow/base/utils.d.ts +0 -20
- package/dist/types/flow/base/utils.d.ts.map +0 -1
- package/dist/types/flow/collections/flowArray.d.ts +0 -148
- package/dist/types/flow/collections/flowArray.d.ts.map +0 -1
- package/dist/types/flow/collections/flowMap.d.ts +0 -224
- package/dist/types/flow/collections/flowMap.d.ts.map +0 -1
- package/dist/types/flow/collections/index.d.ts.map +0 -1
- package/dist/types/flow/index.d.ts +0 -4
- package/dist/types/flow/index.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +0 -137
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +0 -137
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +0 -343
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +0 -81
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +0 -111
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/index.d.ts.map +0 -1
- package/dist/types/flow/nodes/index.d.ts +0 -3
- package/dist/types/flow/nodes/index.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +0 -108
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +0 -100
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowNode.d.ts +0 -314
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +0 -57
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowState.d.ts +0 -96
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/index.d.ts.map +0 -1
- package/dist/types/solid/converters.d.ts +0 -57
- package/dist/types/solid/converters.d.ts.map +0 -1
- package/dist/types/solid/index.d.ts +0 -3
- package/dist/types/solid/index.d.ts.map +0 -1
- package/dist/types/solid/primitives.d.ts +0 -181
- package/dist/types/solid/primitives.d.ts.map +0 -1
- package/docs/api/classes/FlowArray.md +0 -489
- package/docs/api/classes/FlowConstant.md +0 -350
- package/docs/api/classes/FlowDerivation.md +0 -334
- package/docs/api/classes/FlowEffect.md +0 -100
- package/docs/api/classes/FlowMap.md +0 -512
- package/docs/api/classes/FlowObservable.md +0 -306
- package/docs/api/classes/FlowResource.md +0 -380
- package/docs/api/classes/FlowResourceAsync.md +0 -362
- package/docs/api/classes/FlowSignal.md +0 -160
- package/docs/api/classes/FlowState.md +0 -368
- package/docs/api/classes/FlowStream.md +0 -367
- package/docs/api/classes/FlowStreamAsync.md +0 -364
- package/docs/api/classes/SolidDerivation.md +0 -75
- package/docs/api/classes/SolidResource.md +0 -91
- package/docs/api/classes/SolidState.md +0 -71
- package/docs/api/classes/TrackingContext.md +0 -33
- package/docs/api/functions/effect.md +0 -49
- package/docs/api/functions/resource.md +0 -52
- package/docs/api/functions/resourceAsync.md +0 -50
- package/docs/api/functions/stream.md +0 -53
- package/docs/api/functions/streamAsync.md +0 -50
- package/docs/api/interfaces/SolidObservable.md +0 -19
- package/docs/api/type-aliases/FlowStreamDisposer.md +0 -15
- package/docs/api/type-aliases/FlowStreamSetter.md +0 -27
- package/docs/api/type-aliases/FlowStreamUpdater.md +0 -32
- package/docs/api/type-aliases/SolidGetter.md +0 -17
- package/docs/guide/primitives/resources.md +0 -858
- package/docs/guide/primitives/streams.md +0 -931
- package/src/flow/base/flowDisposable.ts +0 -71
- package/src/flow/base/flowEffect.ts +0 -171
- package/src/flow/base/flowGraph.ts +0 -288
- package/src/flow/base/flowSignal.ts +0 -207
- package/src/flow/base/flowTracker.ts +0 -17
- package/src/flow/base/index.ts +0 -6
- package/src/flow/base/utils.ts +0 -19
- package/src/flow/collections/flowArray.ts +0 -409
- package/src/flow/collections/flowMap.ts +0 -398
- package/src/flow/nodes/async/flowConstantAsync.ts +0 -142
- package/src/flow/nodes/async/flowDerivationAsync.ts +0 -143
- package/src/flow/nodes/async/flowNodeAsync.ts +0 -474
- package/src/flow/nodes/async/flowReadonlyAsync.ts +0 -81
- package/src/flow/nodes/async/flowStateAsync.ts +0 -116
- package/src/flow/nodes/await/advanced/index.ts +0 -5
- package/src/flow/nodes/await/advanced/resource.ts +0 -134
- package/src/flow/nodes/await/advanced/resourceAsync.ts +0 -109
- package/src/flow/nodes/await/advanced/stream.ts +0 -188
- package/src/flow/nodes/await/advanced/streamAsync.ts +0 -176
- package/src/flow/nodes/await/flowConstantAwait.ts +0 -154
- package/src/flow/nodes/await/flowDerivationAwait.ts +0 -154
- package/src/flow/nodes/await/flowNodeAwait.ts +0 -508
- package/src/flow/nodes/await/flowReadonlyAwait.ts +0 -89
- package/src/flow/nodes/await/flowStateAwait.ts +0 -130
- package/src/flow/nodes/await/index.ts +0 -5
- package/src/flow/nodes/index.ts +0 -3
- package/src/flow/nodes/sync/flowConstant.ts +0 -111
- package/src/flow/nodes/sync/flowDerivation.ts +0 -105
- package/src/flow/nodes/sync/flowNode.ts +0 -439
- package/src/flow/nodes/sync/flowReadonly.ts +0 -57
- package/src/flow/nodes/sync/flowState.ts +0 -101
- package/src/solid/converters.ts +0 -148
- package/src/solid/index.ts +0 -2
- package/src/solid/primitives.ts +0 -215
- package/test/base/flowEffect.test.ts +0 -108
- package/test/base/flowGraph.test.ts +0 -485
- package/test/base/flowSignal.test.ts +0 -372
- package/test/collections/flowArray.asyncStates.test.ts +0 -1553
- package/test/collections/flowArray.scalars.test.ts +0 -1129
- package/test/collections/flowArray.states.test.ts +0 -1365
- package/test/collections/flowMap.asyncStates.test.ts +0 -1105
- package/test/collections/flowMap.scalars.test.ts +0 -877
- package/test/collections/flowMap.states.test.ts +0 -1097
- package/test/nodes/async/flowConstantAsync.test.ts +0 -860
- package/test/nodes/async/flowDerivationAsync.test.ts +0 -1517
- package/test/nodes/async/flowStateAsync.test.ts +0 -1387
- package/test/nodes/await/advanced/resource.test.ts +0 -129
- package/test/nodes/await/advanced/resourceAsync.test.ts +0 -108
- package/test/nodes/await/advanced/stream.test.ts +0 -198
- package/test/nodes/await/advanced/streamAsync.test.ts +0 -196
- package/test/nodes/await/flowConstantAwait.test.ts +0 -643
- package/test/nodes/await/flowDerivationAwait.test.ts +0 -1583
- package/test/nodes/await/flowStateAwait.test.ts +0 -999
- package/test/nodes/mixed/derivation.test.ts +0 -1527
- package/test/nodes/sync/flowConstant.test.ts +0 -620
- package/test/nodes/sync/flowDerivation.test.ts +0 -1373
- package/test/nodes/sync/flowState.test.ts +0 -945
- package/test/solid/converters.test.ts +0 -721
- package/test/solid/primitives.test.ts +0 -1031
- /package/dist/types/{flow → api/nodes}/collections/index.d.ts +0 -0
- /package/docs/guide/advanced/{upgrading.md → migration-v1.md} +0 -0
- /package/src/{flow → api/nodes}/collections/index.ts +0 -0
|
@@ -1,931 +0,0 @@
|
|
|
1
|
-
# Streams
|
|
2
|
-
|
|
3
|
-
Streams are reactive primitives for handling continuous, event-driven data. Unlike resources that you fetch on demand, streams **push** updates to you whenever new data arrives.
|
|
4
|
-
|
|
5
|
-
## Understanding Streams
|
|
6
|
-
|
|
7
|
-
Think of a stream like a **river of data** - it flows continuously, and you dip your cup in whenever you want the latest value. Or like a **news feed** - stories arrive as they happen, and you're notified of each new one.
|
|
8
|
-
|
|
9
|
-
### Streams vs Resources vs State
|
|
10
|
-
|
|
11
|
-
| Feature | State | Resource | Stream |
|
|
12
|
-
|---------|-------|----------|--------|
|
|
13
|
-
| Data flow | You set it | You pull (fetch) | It pushes to you |
|
|
14
|
-
| Updates | Manual (`.set()`) | Manual (`.fetch()`) | Automatic (events) |
|
|
15
|
-
| Use case | User input, toggles | API calls, file loading | WebSockets, events, timers |
|
|
16
|
-
| Initial value | Required | Optional | Optional (undefined until first update) |
|
|
17
|
-
| Cleanup | None needed | None needed | **Must return disposer** |
|
|
18
|
-
|
|
19
|
-
```mermaid
|
|
20
|
-
flowchart LR
|
|
21
|
-
A[State] -->|You control| B[Value]
|
|
22
|
-
C[Resource] -->|You request| D[Fetch] -->|Returns| B
|
|
23
|
-
E[External Event] -->|Pushes| F[Stream] -->|Updates| B
|
|
24
|
-
|
|
25
|
-
style A fill:#FFE6E6
|
|
26
|
-
style C fill:#E6F3FF
|
|
27
|
-
style F fill:#E6FFE6
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## When to Use Streams
|
|
31
|
-
|
|
32
|
-
Use streams when:
|
|
33
|
-
- ✅ Data comes from external events (WebSockets, DOM events)
|
|
34
|
-
- ✅ Updates happen continuously (timers, intervals)
|
|
35
|
-
- ✅ You don't control when data arrives
|
|
36
|
-
- ✅ You want push-based reactivity
|
|
37
|
-
|
|
38
|
-
Use resources when:
|
|
39
|
-
- ✅ You initiate the data fetch
|
|
40
|
-
- ✅ Data is pulled on demand
|
|
41
|
-
- ✅ You control when to refetch
|
|
42
|
-
|
|
43
|
-
Use state when:
|
|
44
|
-
- ✅ You set the value directly
|
|
45
|
-
- ✅ Updates come from user actions
|
|
46
|
-
- ✅ Data is local to your application
|
|
47
|
-
|
|
48
|
-
## Creating Streams
|
|
49
|
-
|
|
50
|
-
The basic syntax:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { stream } from '@ersbeth/picoflow'
|
|
54
|
-
|
|
55
|
-
const $stream = stream(
|
|
56
|
-
(set) => {
|
|
57
|
-
// Setup: Subscribe to events, start timers, etc.
|
|
58
|
-
// Call set(value) whenever you have new data
|
|
59
|
-
|
|
60
|
-
// Return cleanup function
|
|
61
|
-
return () => {
|
|
62
|
-
// Cleanup: Unsubscribe, clear timers, close connections
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
initialValue // Optional initial value
|
|
66
|
-
)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Anatomy of a Stream
|
|
70
|
-
|
|
71
|
-
```mermaid
|
|
72
|
-
flowchart TD
|
|
73
|
-
A[Create stream] --> B[Execute updater function]
|
|
74
|
-
B --> C[Setup subscriptions/timers]
|
|
75
|
-
C --> D[Return disposer function]
|
|
76
|
-
D --> E[Wait for events]
|
|
77
|
-
E --> F{Event occurs}
|
|
78
|
-
F -->|Yes| G[Call set new value]
|
|
79
|
-
G --> H[Notify dependents]
|
|
80
|
-
H --> E
|
|
81
|
-
F -->|dispose called| I[Run disposer]
|
|
82
|
-
I --> J[Cleanup complete]
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### The `set` Callback
|
|
86
|
-
|
|
87
|
-
Inside your updater function, you receive a `set` callback. Call it with a new value whenever you want to update the stream:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
const $ticker = stream((set) => {
|
|
91
|
-
// Call set() to update the value
|
|
92
|
-
const interval = setInterval(() => {
|
|
93
|
-
set(Date.now())
|
|
94
|
-
}, 1000)
|
|
95
|
-
|
|
96
|
-
return () => clearInterval(interval)
|
|
97
|
-
}, Date.now())
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### The Disposer
|
|
101
|
-
|
|
102
|
-
The function you return is crucial - it cleans up resources when the stream is disposed:
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
const $messages = stream((set) => {
|
|
106
|
-
const ws = new WebSocket('ws://example.com')
|
|
107
|
-
|
|
108
|
-
ws.onmessage = (event) => {
|
|
109
|
-
set(event.data) // Update stream with each message
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// IMPORTANT: Return cleanup function!
|
|
113
|
-
return () => {
|
|
114
|
-
ws.close() // Clean up WebSocket connection
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// Later, cleanup
|
|
119
|
-
$messages.dispose() // Calls the disposer, closes WebSocket
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Basic Examples
|
|
123
|
-
|
|
124
|
-
### Example 1: Timer
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
import { stream, effect } from '@ersbeth/picoflow'
|
|
128
|
-
|
|
129
|
-
const $timer = stream((set) => {
|
|
130
|
-
let count = 0
|
|
131
|
-
const interval = setInterval(() => {
|
|
132
|
-
set(++count)
|
|
133
|
-
}, 1000)
|
|
134
|
-
|
|
135
|
-
return () => clearInterval(interval)
|
|
136
|
-
}, 0)
|
|
137
|
-
|
|
138
|
-
effect((t) => {
|
|
139
|
-
console.log('Timer:', $timer.get(t))
|
|
140
|
-
})
|
|
141
|
-
// Logs: "Timer: 0", "Timer: 1", "Timer: 2", ...
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Example 2: Current Time
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
const $currentTime = stream((set) => {
|
|
148
|
-
// Update every second
|
|
149
|
-
const interval = setInterval(() => {
|
|
150
|
-
set(new Date())
|
|
151
|
-
}, 1000)
|
|
152
|
-
|
|
153
|
-
return () => clearInterval(interval)
|
|
154
|
-
}, new Date())
|
|
155
|
-
|
|
156
|
-
effect((t) => {
|
|
157
|
-
const time = $currentTime.get(t)
|
|
158
|
-
document.getElementById('clock').textContent = time.toLocaleTimeString()
|
|
159
|
-
})
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Example 3: Mouse Position
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
interface Point {
|
|
166
|
-
x: number
|
|
167
|
-
y: number
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const $mousePosition = stream<Point>((set) => {
|
|
171
|
-
function handleMouseMove(event: MouseEvent) {
|
|
172
|
-
set({ x: event.clientX, y: event.clientY })
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
window.addEventListener('mousemove', handleMouseMove)
|
|
176
|
-
|
|
177
|
-
return () => {
|
|
178
|
-
window.removeEventListener('mousemove', handleMouseMove)
|
|
179
|
-
}
|
|
180
|
-
}, { x: 0, y: 0 })
|
|
181
|
-
|
|
182
|
-
effect((t) => {
|
|
183
|
-
const pos = $mousePosition.get(t)
|
|
184
|
-
console.log(`Mouse at (${pos.x}, ${pos.y})`)
|
|
185
|
-
})
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## Asynchronous Streams
|
|
189
|
-
|
|
190
|
-
Async streams handle Promise-based values:
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
import { streamAsync } from '@ersbeth/picoflow'
|
|
194
|
-
|
|
195
|
-
const $asyncData = streamAsync((set) => {
|
|
196
|
-
async function fetchData() {
|
|
197
|
-
const response = await fetch('/api/data')
|
|
198
|
-
const data = await response.json()
|
|
199
|
-
set(data)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const interval = setInterval(fetchData, 5000)
|
|
203
|
-
fetchData() // Initial fetch
|
|
204
|
-
|
|
205
|
-
return () => clearInterval(interval)
|
|
206
|
-
})
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Use with `await` in effects:
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
effect(async (t) => {
|
|
213
|
-
const data = await $asyncData.get(t)
|
|
214
|
-
console.log('Data:', data)
|
|
215
|
-
})
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Stream Lifecycle
|
|
219
|
-
|
|
220
|
-
```mermaid
|
|
221
|
-
stateDiagram-v2
|
|
222
|
-
[*] --> Created: stream((set) => ...)
|
|
223
|
-
Created --> Active: Execute updater
|
|
224
|
-
Active --> Active: set() called → update value
|
|
225
|
-
Active --> Disposing: .dispose() called
|
|
226
|
-
Disposing --> Disposed: Execute disposer function
|
|
227
|
-
Disposed --> [*]
|
|
228
|
-
|
|
229
|
-
note right of Active: Event handlers listening<br/>Timers running<br/>Connections open
|
|
230
|
-
note right of Disposed: All cleanup complete<br/>No more updates
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## Push-Based Data Flow
|
|
234
|
-
|
|
235
|
-
Here's how streams push data compared to pull-based resources:
|
|
236
|
-
|
|
237
|
-
```mermaid
|
|
238
|
-
sequenceDiagram
|
|
239
|
-
participant Event as External Event
|
|
240
|
-
participant Stream as Stream
|
|
241
|
-
participant Effect as Effect
|
|
242
|
-
|
|
243
|
-
Note over Stream: Stream created
|
|
244
|
-
Stream->>Stream: Setup subscriptions
|
|
245
|
-
|
|
246
|
-
Note over Event,Effect: Data flows automatically
|
|
247
|
-
Event->>Stream: Event occurs
|
|
248
|
-
Stream->>Stream: Call set(value)
|
|
249
|
-
Stream->>Effect: Notify of change
|
|
250
|
-
Effect->>Stream: .get(t)
|
|
251
|
-
Stream->>Effect: Return latest value
|
|
252
|
-
|
|
253
|
-
Note over Event,Effect: More events...
|
|
254
|
-
Event->>Stream: Another event
|
|
255
|
-
Stream->>Stream: Call set(value)
|
|
256
|
-
Stream->>Effect: Notify again
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Compare to pull-based resources:
|
|
260
|
-
|
|
261
|
-
```mermaid
|
|
262
|
-
sequenceDiagram
|
|
263
|
-
participant You as Your Code
|
|
264
|
-
participant Resource as Resource
|
|
265
|
-
participant API as External API
|
|
266
|
-
|
|
267
|
-
Note over You,API: You control when to fetch
|
|
268
|
-
You->>Resource: .fetch()
|
|
269
|
-
Resource->>API: Request data
|
|
270
|
-
API->>Resource: Return data
|
|
271
|
-
Resource->>Resource: Update value
|
|
272
|
-
Resource->>You: Done
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
## Practical Examples
|
|
276
|
-
|
|
277
|
-
### Example 1: WebSocket Connection
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
interface Message {
|
|
281
|
-
type: string
|
|
282
|
-
content: string
|
|
283
|
-
timestamp: number
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const $messages = stream<Message>((set) => {
|
|
287
|
-
const ws = new WebSocket('wss://example.com/socket')
|
|
288
|
-
|
|
289
|
-
ws.onopen = () => {
|
|
290
|
-
console.log('WebSocket connected')
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
ws.onmessage = (event) => {
|
|
294
|
-
const message = JSON.parse(event.data)
|
|
295
|
-
set(message)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
ws.onerror = (error) => {
|
|
299
|
-
console.error('WebSocket error:', error)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
ws.onclose = () => {
|
|
303
|
-
console.log('WebSocket closed')
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Cleanup
|
|
307
|
-
return () => {
|
|
308
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
309
|
-
ws.close()
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
// Display messages
|
|
315
|
-
effect((t) => {
|
|
316
|
-
const message = $messages.get(t)
|
|
317
|
-
if (message) {
|
|
318
|
-
displayMessage(message)
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Example 2: Keyboard Events
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
const $keyPressed = stream<string>((set) => {
|
|
327
|
-
function handleKeyDown(event: KeyboardEvent) {
|
|
328
|
-
set(event.key)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
window.addEventListener('keydown', handleKeyDown)
|
|
332
|
-
|
|
333
|
-
return () => {
|
|
334
|
-
window.removeEventListener('keydown', handleKeyDown)
|
|
335
|
-
}
|
|
336
|
-
}, '')
|
|
337
|
-
|
|
338
|
-
effect((t) => {
|
|
339
|
-
const key = $keyPressed.get(t)
|
|
340
|
-
console.log('Key pressed:', key)
|
|
341
|
-
})
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### Example 3: Countdown Timer
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
const $countdown = stream((set) => {
|
|
348
|
-
let remaining = 60 // 60 seconds
|
|
349
|
-
set(remaining)
|
|
350
|
-
|
|
351
|
-
const interval = setInterval(() => {
|
|
352
|
-
remaining--
|
|
353
|
-
set(remaining)
|
|
354
|
-
|
|
355
|
-
if (remaining <= 0) {
|
|
356
|
-
clearInterval(interval)
|
|
357
|
-
}
|
|
358
|
-
}, 1000)
|
|
359
|
-
|
|
360
|
-
return () => clearInterval(interval)
|
|
361
|
-
}, 60)
|
|
362
|
-
|
|
363
|
-
effect((t) => {
|
|
364
|
-
const time = $countdown.get(t)
|
|
365
|
-
document.getElementById('countdown').textContent = `${time}s`
|
|
366
|
-
|
|
367
|
-
if (time === 0) {
|
|
368
|
-
alert('Time is up!')
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
### Example 4: Real-time Notifications
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
interface Notification {
|
|
377
|
-
id: string
|
|
378
|
-
title: string
|
|
379
|
-
message: string
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const $notifications = stream<Notification[]>((set) => {
|
|
383
|
-
const notifications: Notification[] = []
|
|
384
|
-
|
|
385
|
-
// Server-Sent Events
|
|
386
|
-
const eventSource = new EventSource('/api/notifications/stream')
|
|
387
|
-
|
|
388
|
-
eventSource.onmessage = (event) => {
|
|
389
|
-
const notification = JSON.parse(event.data)
|
|
390
|
-
notifications.push(notification)
|
|
391
|
-
set([...notifications]) // Update with new array
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
eventSource.onerror = (error) => {
|
|
395
|
-
console.error('SSE error:', error)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return () => {
|
|
399
|
-
eventSource.close()
|
|
400
|
-
}
|
|
401
|
-
}, [])
|
|
402
|
-
|
|
403
|
-
// Show notifications
|
|
404
|
-
effect((t) => {
|
|
405
|
-
const notifications = $notifications.get(t)
|
|
406
|
-
displayNotifications(notifications)
|
|
407
|
-
})
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Example 5: Window Resize
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
interface WindowSize {
|
|
414
|
-
width: number
|
|
415
|
-
height: number
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const $windowSize = stream<WindowSize>((set) => {
|
|
419
|
-
function updateSize() {
|
|
420
|
-
set({
|
|
421
|
-
width: window.innerWidth,
|
|
422
|
-
height: window.innerHeight
|
|
423
|
-
})
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Initial size
|
|
427
|
-
updateSize()
|
|
428
|
-
|
|
429
|
-
// Listen for changes
|
|
430
|
-
window.addEventListener('resize', updateSize)
|
|
431
|
-
|
|
432
|
-
return () => {
|
|
433
|
-
window.removeEventListener('resize', updateSize)
|
|
434
|
-
}
|
|
435
|
-
}, { width: window.innerWidth, height: window.innerHeight })
|
|
436
|
-
|
|
437
|
-
// Respond to size changes
|
|
438
|
-
effect((t) => {
|
|
439
|
-
const size = $windowSize.get(t)
|
|
440
|
-
console.log(`Window: ${size.width}x${size.height}`)
|
|
441
|
-
|
|
442
|
-
if (size.width < 768) {
|
|
443
|
-
enableMobileLayout()
|
|
444
|
-
} else {
|
|
445
|
-
enableDesktopLayout()
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
## Stream Patterns
|
|
451
|
-
|
|
452
|
-
### Pattern 1: Filtered Stream
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
// Only emit values that pass a condition
|
|
456
|
-
function filteredStream<T>(
|
|
457
|
-
setup: (set: (value: T) => void) => () => void,
|
|
458
|
-
predicate: (value: T) => boolean,
|
|
459
|
-
initial?: T
|
|
460
|
-
) {
|
|
461
|
-
return stream<T>((set) => {
|
|
462
|
-
return setup((value) => {
|
|
463
|
-
if (predicate(value)) {
|
|
464
|
-
set(value)
|
|
465
|
-
}
|
|
466
|
-
})
|
|
467
|
-
}, initial)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Usage
|
|
471
|
-
const $significantMoves = filteredStream(
|
|
472
|
-
(set) => {
|
|
473
|
-
let lastX = 0
|
|
474
|
-
function handleMove(event: MouseEvent) {
|
|
475
|
-
// Only emit if mouse moved > 10px
|
|
476
|
-
if (Math.abs(event.clientX - lastX) > 10) {
|
|
477
|
-
lastX = event.clientX
|
|
478
|
-
set(event.clientX)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
window.addEventListener('mousemove', handleMove)
|
|
482
|
-
return () => window.removeEventListener('mousemove', handleMove)
|
|
483
|
-
},
|
|
484
|
-
(x) => true,
|
|
485
|
-
0
|
|
486
|
-
)
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### Pattern 2: Debounced Stream
|
|
490
|
-
|
|
491
|
-
```typescript
|
|
492
|
-
function debouncedStream<T>(
|
|
493
|
-
setup: (set: (value: T) => void) => () => void,
|
|
494
|
-
delay: number,
|
|
495
|
-
initial?: T
|
|
496
|
-
) {
|
|
497
|
-
return stream<T>((set) => {
|
|
498
|
-
let timeout: number | null = null
|
|
499
|
-
|
|
500
|
-
const disposer = setup((value) => {
|
|
501
|
-
if (timeout) clearTimeout(timeout)
|
|
502
|
-
timeout = setTimeout(() => set(value), delay)
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
return () => {
|
|
506
|
-
if (timeout) clearTimeout(timeout)
|
|
507
|
-
disposer()
|
|
508
|
-
}
|
|
509
|
-
}, initial)
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Usage: Debounce input changes
|
|
513
|
-
const $searchQuery = state('')
|
|
514
|
-
const $debouncedQuery = debouncedStream((set) => {
|
|
515
|
-
return effect((t) => {
|
|
516
|
-
set($searchQuery.get(t))
|
|
517
|
-
}).dispose
|
|
518
|
-
}, 300)
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
### Pattern 3: Combined Streams
|
|
522
|
-
|
|
523
|
-
```typescript
|
|
524
|
-
// Merge multiple streams
|
|
525
|
-
function mergeStreams<T>(...streams: FlowStream<T>[]) {
|
|
526
|
-
return stream<T>((set) => {
|
|
527
|
-
const disposers = streams.map(stream => {
|
|
528
|
-
return effect((t) => {
|
|
529
|
-
set(stream.get(t))
|
|
530
|
-
})
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
return () => {
|
|
534
|
-
disposers.forEach(d => d.dispose())
|
|
535
|
-
}
|
|
536
|
-
})
|
|
537
|
-
}
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### Pattern 4: Stream from Observable
|
|
541
|
-
|
|
542
|
-
```typescript
|
|
543
|
-
import { Observable } from 'rxjs'
|
|
544
|
-
|
|
545
|
-
function fromObservable<T>(observable: Observable<T>, initial?: T) {
|
|
546
|
-
return stream<T>((set) => {
|
|
547
|
-
const subscription = observable.subscribe({
|
|
548
|
-
next: (value) => set(value),
|
|
549
|
-
error: (error) => console.error('Observable error:', error)
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
return () => subscription.unsubscribe()
|
|
553
|
-
}, initial)
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Usage
|
|
557
|
-
import { interval } from 'rxjs'
|
|
558
|
-
const $rxTimer = fromObservable(interval(1000), 0)
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Cleanup and Disposal
|
|
562
|
-
|
|
563
|
-
**The most important rule:** Always return a disposer function!
|
|
564
|
-
|
|
565
|
-
### Why Cleanup Matters
|
|
566
|
-
|
|
567
|
-
```mermaid
|
|
568
|
-
flowchart TD
|
|
569
|
-
A[Stream Created] --> B[Subscriptions Active]
|
|
570
|
-
B --> C[Timers Running]
|
|
571
|
-
C --> D[Connections Open]
|
|
572
|
-
D --> E{Stream Disposed?}
|
|
573
|
-
E -->|No| F[⚠️ MEMORY LEAK]
|
|
574
|
-
E -->|Yes| G[Run Disposer]
|
|
575
|
-
G --> H[Clear Subscriptions]
|
|
576
|
-
H --> I[Stop Timers]
|
|
577
|
-
I --> J[Close Connections]
|
|
578
|
-
J --> K[✅ Clean]
|
|
579
|
-
|
|
580
|
-
style F fill:#FFE6E6
|
|
581
|
-
style K fill:#E6FFE6
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
### Good Cleanup Examples
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
// ✅ Timer cleanup
|
|
588
|
-
const $timer = stream((set) => {
|
|
589
|
-
const id = setInterval(() => set(Date.now()), 1000)
|
|
590
|
-
return () => clearInterval(id) // Cleanup!
|
|
591
|
-
}, Date.now())
|
|
592
|
-
|
|
593
|
-
// ✅ Event listener cleanup
|
|
594
|
-
const $clicks = stream((set) => {
|
|
595
|
-
const handler = () => set(Date.now())
|
|
596
|
-
window.addEventListener('click', handler)
|
|
597
|
-
return () => window.removeEventListener('click', handler) // Cleanup!
|
|
598
|
-
})
|
|
599
|
-
|
|
600
|
-
// ✅ WebSocket cleanup
|
|
601
|
-
const $ws = stream((set) => {
|
|
602
|
-
const socket = new WebSocket('ws://example.com')
|
|
603
|
-
socket.onmessage = (e) => set(e.data)
|
|
604
|
-
return () => socket.close() // Cleanup!
|
|
605
|
-
})
|
|
606
|
-
|
|
607
|
-
// ✅ Multiple cleanups
|
|
608
|
-
const $multi = stream((set) => {
|
|
609
|
-
const interval = setInterval(() => set('tick'), 1000)
|
|
610
|
-
const handler = () => set('click')
|
|
611
|
-
window.addEventListener('click', handler)
|
|
612
|
-
|
|
613
|
-
return () => {
|
|
614
|
-
clearInterval(interval)
|
|
615
|
-
window.removeEventListener('click', handler)
|
|
616
|
-
} // Cleanup both!
|
|
617
|
-
})
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
### Bad Cleanup Examples
|
|
621
|
-
|
|
622
|
-
```typescript
|
|
623
|
-
// ❌ No cleanup - memory leak!
|
|
624
|
-
const $bad = stream((set) => {
|
|
625
|
-
setInterval(() => set(Date.now()), 1000)
|
|
626
|
-
// Missing: return () => clearInterval(...)
|
|
627
|
-
})
|
|
628
|
-
|
|
629
|
-
// ❌ Cleanup doesn't work
|
|
630
|
-
const $broken = stream((set) => {
|
|
631
|
-
setInterval(() => set(Date.now()), 1000)
|
|
632
|
-
return () => {
|
|
633
|
-
clearInterval(123) // Wrong ID!
|
|
634
|
-
}
|
|
635
|
-
})
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
## Error Handling
|
|
639
|
-
|
|
640
|
-
### Basic Error Handling
|
|
641
|
-
|
|
642
|
-
```typescript
|
|
643
|
-
const $data = streamAsync((set) => {
|
|
644
|
-
const ws = new WebSocket('wss://example.com')
|
|
645
|
-
|
|
646
|
-
ws.onmessage = async (event) => {
|
|
647
|
-
try {
|
|
648
|
-
const data = JSON.parse(event.data)
|
|
649
|
-
set(data)
|
|
650
|
-
} catch (error) {
|
|
651
|
-
console.error('Failed to parse message:', error)
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
ws.onerror = (error) => {
|
|
656
|
-
console.error('WebSocket error:', error)
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
return () => ws.close()
|
|
660
|
-
})
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
### With Error State
|
|
664
|
-
|
|
665
|
-
```typescript
|
|
666
|
-
const $wsData = stream<any>((set) => {
|
|
667
|
-
const ws = new WebSocket('wss://example.com')
|
|
668
|
-
|
|
669
|
-
ws.onopen = () => $wsStatus.set('connected')
|
|
670
|
-
ws.onclose = () => $wsStatus.set('disconnected')
|
|
671
|
-
ws.onerror = () => $wsStatus.set('error')
|
|
672
|
-
ws.onmessage = (e) => set(JSON.parse(e.data))
|
|
673
|
-
|
|
674
|
-
return () => ws.close()
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
const $wsStatus = state<'connected' | 'disconnected' | 'error'>('disconnected')
|
|
678
|
-
|
|
679
|
-
effect((t) => {
|
|
680
|
-
const status = $wsStatus.get(t)
|
|
681
|
-
|
|
682
|
-
switch (status) {
|
|
683
|
-
case 'connected':
|
|
684
|
-
showConnected()
|
|
685
|
-
break
|
|
686
|
-
case 'error':
|
|
687
|
-
showError('Connection failed')
|
|
688
|
-
break
|
|
689
|
-
case 'disconnected':
|
|
690
|
-
showDisconnected()
|
|
691
|
-
break
|
|
692
|
-
}
|
|
693
|
-
})
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
## Best Practices
|
|
697
|
-
|
|
698
|
-
### 1. Always Return a Disposer
|
|
699
|
-
|
|
700
|
-
```typescript
|
|
701
|
-
// ✅ Good
|
|
702
|
-
const $stream = stream((set) => {
|
|
703
|
-
const id = setInterval(() => set(Date.now()), 1000)
|
|
704
|
-
return () => clearInterval(id)
|
|
705
|
-
}, Date.now())
|
|
706
|
-
|
|
707
|
-
// ❌ Bad - no cleanup
|
|
708
|
-
const $stream = stream((set) => {
|
|
709
|
-
setInterval(() => set(Date.now()), 1000)
|
|
710
|
-
// Missing return!
|
|
711
|
-
}, Date.now())
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
### 2. Provide Initial Values
|
|
715
|
-
|
|
716
|
-
```typescript
|
|
717
|
-
// ✅ Good - immediate value available
|
|
718
|
-
const $time = stream((set) => {
|
|
719
|
-
const id = setInterval(() => set(Date.now()), 1000)
|
|
720
|
-
return () => clearInterval(id)
|
|
721
|
-
}, Date.now()) // Initial value
|
|
722
|
-
|
|
723
|
-
// ⚠️ Okay but requires undefined check
|
|
724
|
-
const $time = stream((set) => {
|
|
725
|
-
const id = setInterval(() => set(Date.now()), 1000)
|
|
726
|
-
return () => clearInterval(id)
|
|
727
|
-
}) // undefined initially
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
### 3. Handle Connection Errors
|
|
731
|
-
|
|
732
|
-
```typescript
|
|
733
|
-
const $ws = stream((set) => {
|
|
734
|
-
const ws = new WebSocket('wss://example.com')
|
|
735
|
-
|
|
736
|
-
ws.onerror = (error) => {
|
|
737
|
-
console.error('WebSocket error:', error)
|
|
738
|
-
// Maybe set error state or reconnect
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
ws.onmessage = (e) => set(e.data)
|
|
742
|
-
|
|
743
|
-
return () => ws.close()
|
|
744
|
-
})
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
### 4. Avoid Duplicate Subscriptions
|
|
748
|
-
|
|
749
|
-
```typescript
|
|
750
|
-
// ❌ Bad - creates new stream on every render
|
|
751
|
-
function MyComponent() {
|
|
752
|
-
const $stream = stream((set) => { /* ... */ })
|
|
753
|
-
// New stream created each time!
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// ✅ Good - create stream once
|
|
757
|
-
const $stream = stream((set) => { /* ... */ })
|
|
758
|
-
|
|
759
|
-
function MyComponent() {
|
|
760
|
-
// Use existing stream
|
|
761
|
-
}
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
### 5. Clean Up in Components
|
|
765
|
-
|
|
766
|
-
```typescript
|
|
767
|
-
// React example
|
|
768
|
-
function useStream<T>(stream: FlowStream<T>) {
|
|
769
|
-
useEffect(() => {
|
|
770
|
-
return () => {
|
|
771
|
-
stream.dispose() // Cleanup when component unmounts
|
|
772
|
-
}
|
|
773
|
-
}, [stream])
|
|
774
|
-
|
|
775
|
-
// ... use stream
|
|
776
|
-
}
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
## Comparing: Stream vs Resource
|
|
780
|
-
|
|
781
|
-
```mermaid
|
|
782
|
-
flowchart TD
|
|
783
|
-
A{Who initiates updates?} -->|You do pull| B[Use Resource]
|
|
784
|
-
A -->|External pushes| C[Use Stream]
|
|
785
|
-
B --> D[.fetch to get data]
|
|
786
|
-
C --> E[.set called by events]
|
|
787
|
-
D --> F[HTTP APIs<br/>File reads<br/>Database queries]
|
|
788
|
-
E --> G[WebSockets<br/>DOM events<br/>Timers<br/>Server-Sent Events]
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
## Common Pitfalls
|
|
792
|
-
|
|
793
|
-
### Pitfall 1: Forgetting Cleanup
|
|
794
|
-
|
|
795
|
-
```typescript
|
|
796
|
-
// ❌ Memory leak!
|
|
797
|
-
const $bad = stream((set) => {
|
|
798
|
-
setInterval(() => set(Date.now()), 1000)
|
|
799
|
-
})
|
|
800
|
-
|
|
801
|
-
// ✅ Proper cleanup
|
|
802
|
-
const $good = stream((set) => {
|
|
803
|
-
const id = setInterval(() => set(Date.now()), 1000)
|
|
804
|
-
return () => clearInterval(id)
|
|
805
|
-
}, Date.now())
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### Pitfall 2: Wrong Cleanup Reference
|
|
809
|
-
|
|
810
|
-
```typescript
|
|
811
|
-
// ❌ Loses reference to interval ID
|
|
812
|
-
const $bad = stream((set) => {
|
|
813
|
-
setInterval(() => set(Date.now()), 1000)
|
|
814
|
-
return () => clearInterval(999) // Wrong ID!
|
|
815
|
-
}, Date.now())
|
|
816
|
-
|
|
817
|
-
// ✅ Correct reference
|
|
818
|
-
const $good = stream((set) => {
|
|
819
|
-
const id = setInterval(() => set(Date.now()), 1000)
|
|
820
|
-
return () => clearInterval(id) // Correct ID
|
|
821
|
-
}, Date.now())
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
### Pitfall 3: Not Handling Errors
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
// ❌ Unhandled errors crash app
|
|
828
|
-
const $bad = stream((set) => {
|
|
829
|
-
const ws = new WebSocket('wss://example.com')
|
|
830
|
-
ws.onmessage = (e) => set(JSON.parse(e.data)) // Might throw!
|
|
831
|
-
return () => ws.close()
|
|
832
|
-
})
|
|
833
|
-
|
|
834
|
-
// ✅ Error handling
|
|
835
|
-
const $good = stream((set) => {
|
|
836
|
-
const ws = new WebSocket('wss://example.com')
|
|
837
|
-
ws.onmessage = (e) => {
|
|
838
|
-
try {
|
|
839
|
-
set(JSON.parse(e.data))
|
|
840
|
-
} catch (error) {
|
|
841
|
-
console.error('Parse error:', error)
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
ws.onerror = (error) => console.error('WS error:', error)
|
|
845
|
-
return () => ws.close()
|
|
846
|
-
})
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
## Complete Example: Live Chat
|
|
850
|
-
|
|
851
|
-
```typescript
|
|
852
|
-
import { stream, state, derivation, effect } from '@ersbeth/picoflow'
|
|
853
|
-
|
|
854
|
-
interface ChatMessage {
|
|
855
|
-
id: string
|
|
856
|
-
user: string
|
|
857
|
-
text: string
|
|
858
|
-
timestamp: number
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// WebSocket stream for incoming messages
|
|
862
|
-
const $incomingMessages = stream<ChatMessage>((set) => {
|
|
863
|
-
const ws = new WebSocket('wss://chat.example.com')
|
|
864
|
-
|
|
865
|
-
ws.onopen = () => {
|
|
866
|
-
console.log('Chat connected')
|
|
867
|
-
$connected.set(true)
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
ws.onmessage = (event) => {
|
|
871
|
-
const message = JSON.parse(event.data)
|
|
872
|
-
set(message)
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
ws.onerror = (error) => {
|
|
876
|
-
console.error('Chat error:', error)
|
|
877
|
-
$connected.set(false)
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
ws.onclose = () => {
|
|
881
|
-
console.log('Chat disconnected')
|
|
882
|
-
$connected.set(false)
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
return () => {
|
|
886
|
-
ws.close()
|
|
887
|
-
}
|
|
888
|
-
})
|
|
889
|
-
|
|
890
|
-
// Connection state
|
|
891
|
-
const $connected = state(false)
|
|
892
|
-
|
|
893
|
-
// All messages
|
|
894
|
-
const $messages = state<ChatMessage[]>([])
|
|
895
|
-
|
|
896
|
-
// Add incoming messages to list
|
|
897
|
-
effect((t) => {
|
|
898
|
-
const newMessage = $incomingMessages.get(t)
|
|
899
|
-
if (newMessage) {
|
|
900
|
-
$messages.set(messages => [...messages, newMessage])
|
|
901
|
-
}
|
|
902
|
-
})
|
|
903
|
-
|
|
904
|
-
// Unread count
|
|
905
|
-
const $lastReadTime = state(Date.now())
|
|
906
|
-
const $unreadCount = derivation((t) => {
|
|
907
|
-
const messages = $messages.get(t)
|
|
908
|
-
const lastRead = $lastReadTime.get(t)
|
|
909
|
-
return messages.filter(m => m.timestamp > lastRead).length
|
|
910
|
-
})
|
|
911
|
-
|
|
912
|
-
// Display messages
|
|
913
|
-
effect((t) => {
|
|
914
|
-
const messages = $messages.get(t)
|
|
915
|
-
const connected = $connected.get(t)
|
|
916
|
-
|
|
917
|
-
renderChatUI(messages, connected)
|
|
918
|
-
})
|
|
919
|
-
|
|
920
|
-
// Update unread badge
|
|
921
|
-
effect((t) => {
|
|
922
|
-
const unread = $unreadCount.get(t)
|
|
923
|
-
document.getElementById('unread-badge').textContent = String(unread)
|
|
924
|
-
document.getElementById('unread-badge').hidden = unread === 0
|
|
925
|
-
})
|
|
926
|
-
|
|
927
|
-
// Mark as read
|
|
928
|
-
function markAllRead() {
|
|
929
|
-
$lastReadTime.set(Date.now())
|
|
930
|
-
}
|
|
931
|
-
```
|