@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,858 +0,0 @@
|
|
|
1
|
-
# Resources
|
|
2
|
-
|
|
3
|
-
Resources are specialized reactive primitives for managing data that needs to be fetched, loaded, or retrieved. They're perfect for API calls, file loading, and any data that requires a fetch operation.
|
|
4
|
-
|
|
5
|
-
## Understanding Resources
|
|
6
|
-
|
|
7
|
-
A **resource** is like a state that knows how to fetch its own data. Instead of manually fetching and setting state, you provide a fetch function and let the resource manage the lifecycle.
|
|
8
|
-
|
|
9
|
-
Think of it like a **self-updating contact card** - instead of manually calling someone to get their latest info, the card knows how to call them and update itself.
|
|
10
|
-
|
|
11
|
-
### Resources vs State
|
|
12
|
-
|
|
13
|
-
| Feature | State | Resource |
|
|
14
|
-
|---------|-------|----------|
|
|
15
|
-
| Initial value | Required | Optional (can be undefined) |
|
|
16
|
-
| How to update | `.set()` manually | `.fetch()` to refetch |
|
|
17
|
-
| Use case | User input, toggles | API data, file loading |
|
|
18
|
-
| Data source | Set by your code | Fetched by provided function |
|
|
19
|
-
|
|
20
|
-
## When to Use Resources
|
|
21
|
-
|
|
22
|
-
Use resources when:
|
|
23
|
-
- ✅ Data comes from an API or external source
|
|
24
|
-
- ✅ You need to refetch data based on triggers
|
|
25
|
-
- ✅ You want built-in loading/undefined states
|
|
26
|
-
- ✅ The fetch logic should be encapsulated
|
|
27
|
-
|
|
28
|
-
Use regular state when:
|
|
29
|
-
- ✅ You control the data directly (form inputs, UI state)
|
|
30
|
-
- ✅ Data doesn't need to be fetched
|
|
31
|
-
- ✅ Updates come from user actions
|
|
32
|
-
|
|
33
|
-
## Synchronous Resources
|
|
34
|
-
|
|
35
|
-
Synchronous resources use a regular function (not async) to fetch data:
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
import { resource } from '@ersbeth/picoflow'
|
|
39
|
-
|
|
40
|
-
const $config = resource(
|
|
41
|
-
() => {
|
|
42
|
-
// Fetch function (synchronous)
|
|
43
|
-
return loadConfigFromFile()
|
|
44
|
-
},
|
|
45
|
-
null // Initial value (optional)
|
|
46
|
-
)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Creating a Resource
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
import { resource, effect } from '@ersbeth/picoflow'
|
|
53
|
-
|
|
54
|
-
// Resource with initial value
|
|
55
|
-
const $userData = resource(
|
|
56
|
-
() => fetchUserFromCache(),
|
|
57
|
-
{ id: 0, name: 'Guest' } // Initial value
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// Resource without initial value (starts as undefined)
|
|
61
|
-
const $data = resource(
|
|
62
|
-
() => loadData(),
|
|
63
|
-
undefined
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// Use in effect
|
|
67
|
-
effect((t) => {
|
|
68
|
-
const data = $data.get(t)
|
|
69
|
-
if (data) {
|
|
70
|
-
console.log('Data loaded:', data)
|
|
71
|
-
} else {
|
|
72
|
-
console.log('No data yet')
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Fetching and Refetching
|
|
78
|
-
|
|
79
|
-
Call `.fetch()` to trigger a data fetch:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
const $posts = resource(
|
|
83
|
-
() => getPosts(),
|
|
84
|
-
[]
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
// Initial fetch
|
|
88
|
-
$posts.fetch()
|
|
89
|
-
|
|
90
|
-
// Later, refetch when needed
|
|
91
|
-
function refreshPosts() {
|
|
92
|
-
$posts.fetch() // Fetches and updates
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Automatic refetch in effect
|
|
96
|
-
const $userId = state(1)
|
|
97
|
-
|
|
98
|
-
effect((t) => {
|
|
99
|
-
const userId = $userId.get(t)
|
|
100
|
-
$posts.fetch() // Refetch when userId changes
|
|
101
|
-
})
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Resource Lifecycle
|
|
105
|
-
|
|
106
|
-
```mermaid
|
|
107
|
-
stateDiagram-v2
|
|
108
|
-
[*] --> Idle: Create resource
|
|
109
|
-
Idle --> Fetching: .fetch() called
|
|
110
|
-
Fetching --> Ready: Fetch complete
|
|
111
|
-
Ready --> Fetching: .fetch() called again
|
|
112
|
-
Ready --> Disposed: .dispose()
|
|
113
|
-
Fetching --> Disposed: .dispose()
|
|
114
|
-
Idle --> Disposed: .dispose()
|
|
115
|
-
Disposed --> [*]
|
|
116
|
-
|
|
117
|
-
note right of Idle: Value is initial value<br/>or undefined
|
|
118
|
-
note right of Ready: Value is fetched data
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Asynchronous Resources
|
|
122
|
-
|
|
123
|
-
Asynchronous resources use async functions and return Promises:
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
import { resourceAsync } from '@ersbeth/picoflow'
|
|
127
|
-
|
|
128
|
-
const $user = resourceAsync(async () => {
|
|
129
|
-
const response = await fetch('/api/user')
|
|
130
|
-
return response.json()
|
|
131
|
-
})
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### How Async Resources Work
|
|
135
|
-
|
|
136
|
-
```mermaid
|
|
137
|
-
flowchart TD
|
|
138
|
-
A[Create resourceAsync] --> B[.fetch called or reactive dependency triggers]
|
|
139
|
-
B --> C[Execute async fetch function]
|
|
140
|
-
C --> D[Return Promise]
|
|
141
|
-
D --> E{Use .get t in effect}
|
|
142
|
-
E --> F[await Promise]
|
|
143
|
-
F --> G[Effect has resolved value]
|
|
144
|
-
|
|
145
|
-
H[Dependency changes] --> B
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The key difference: `.get(t)` returns a **Promise**, not the value directly.
|
|
149
|
-
|
|
150
|
-
### Using Async Resources
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
import { resourceAsync, effect } from '@ersbeth/picoflow'
|
|
154
|
-
|
|
155
|
-
const $posts = resourceAsync(async () => {
|
|
156
|
-
const response = await fetch('https://api.example.com/posts')
|
|
157
|
-
return response.json()
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
// Use with async effect
|
|
161
|
-
effect(async (t) => {
|
|
162
|
-
const posts = await $posts.get(t) // Wait for Promise
|
|
163
|
-
console.log('Posts:', posts)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
// Trigger fetch
|
|
167
|
-
$posts.fetch()
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Reactive Fetching
|
|
171
|
-
|
|
172
|
-
Combine with state for reactive data loading:
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
const $userId = state(1)
|
|
176
|
-
|
|
177
|
-
const $user = resourceAsync(async () => {
|
|
178
|
-
const id = $userId.pick() // Read current user ID
|
|
179
|
-
const response = await fetch(`/api/users/${id}`)
|
|
180
|
-
return response.json()
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
// Refetch when user ID changes
|
|
184
|
-
effect((t) => {
|
|
185
|
-
const userId = $userId.get(t)
|
|
186
|
-
$user.fetch() // Fetch new user data
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
// Display user data
|
|
190
|
-
effect(async (t) => {
|
|
191
|
-
const user = await $user.get(t)
|
|
192
|
-
displayUser(user)
|
|
193
|
-
})
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Handling Undefined State
|
|
197
|
-
|
|
198
|
-
Resources can be undefined initially:
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
const $data = resource(
|
|
202
|
-
() => loadData(),
|
|
203
|
-
undefined // Starts as undefined
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
effect((t) => {
|
|
207
|
-
const data = $data.get(t)
|
|
208
|
-
|
|
209
|
-
if (data === undefined) {
|
|
210
|
-
showLoading()
|
|
211
|
-
} else {
|
|
212
|
-
showData(data)
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
// Trigger fetch
|
|
217
|
-
$data.fetch()
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
For better type safety, use a discriminated union:
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
type DataState<T> =
|
|
224
|
-
| { status: 'loading' }
|
|
225
|
-
| { status: 'loaded'; data: T }
|
|
226
|
-
| { status: 'error'; error: string }
|
|
227
|
-
|
|
228
|
-
const $userState = state<DataState<User>>({ status: 'loading' })
|
|
229
|
-
|
|
230
|
-
const $user = resourceAsync(async () => {
|
|
231
|
-
try {
|
|
232
|
-
$userState.set({ status: 'loading' })
|
|
233
|
-
const data = await fetchUser()
|
|
234
|
-
$userState.set({ status: 'loaded', data })
|
|
235
|
-
return data
|
|
236
|
-
} catch (error) {
|
|
237
|
-
$userState.set({ status: 'error', error: error.message })
|
|
238
|
-
throw error
|
|
239
|
-
}
|
|
240
|
-
})
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## Practical Examples
|
|
244
|
-
|
|
245
|
-
### Example 1: API Data Fetching
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
import { resourceAsync, state, effect } from '@ersbeth/picoflow'
|
|
249
|
-
|
|
250
|
-
interface Post {
|
|
251
|
-
id: number
|
|
252
|
-
title: string
|
|
253
|
-
body: string
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const $posts = resourceAsync(async () => {
|
|
257
|
-
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
|
|
258
|
-
if (!response.ok) throw new Error('Failed to fetch')
|
|
259
|
-
return response.json() as Promise<Post[]>
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
// Loading state
|
|
263
|
-
const $loading = state(false)
|
|
264
|
-
|
|
265
|
-
// Fetch with loading state
|
|
266
|
-
async function fetchPosts() {
|
|
267
|
-
$loading.set(true)
|
|
268
|
-
try {
|
|
269
|
-
await $posts.fetch()
|
|
270
|
-
} finally {
|
|
271
|
-
$loading.set(false)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Display posts
|
|
276
|
-
effect(async (t) => {
|
|
277
|
-
const posts = await $posts.get(t)
|
|
278
|
-
const loading = $loading.get(t)
|
|
279
|
-
|
|
280
|
-
if (loading) {
|
|
281
|
-
showSpinner()
|
|
282
|
-
} else {
|
|
283
|
-
displayPosts(posts)
|
|
284
|
-
}
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
// Initial fetch
|
|
288
|
-
fetchPosts()
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Example 2: User Profile with Dependencies
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
const $userId = state<number | null>(null)
|
|
295
|
-
|
|
296
|
-
const $userProfile = resourceAsync(async () => {
|
|
297
|
-
const userId = $userId.pick()
|
|
298
|
-
if (!userId) throw new Error('No user ID')
|
|
299
|
-
|
|
300
|
-
const response = await fetch(`/api/users/${userId}`)
|
|
301
|
-
return response.json()
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
// Refetch when user ID changes
|
|
305
|
-
effect((t) => {
|
|
306
|
-
const userId = $userId.get(t)
|
|
307
|
-
if (userId) {
|
|
308
|
-
$userProfile.fetch()
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
// Display profile
|
|
313
|
-
effect(async (t) => {
|
|
314
|
-
try {
|
|
315
|
-
const profile = await $userProfile.get(t)
|
|
316
|
-
displayProfile(profile)
|
|
317
|
-
} catch (error) {
|
|
318
|
-
showError('Failed to load user')
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
// Change user
|
|
323
|
-
$userId.set(42) // Automatically triggers refetch
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Example 3: Configuration Loading
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
interface AppConfig {
|
|
330
|
-
apiUrl: string
|
|
331
|
-
features: Record<string, boolean>
|
|
332
|
-
theme: string
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const $config = resource(
|
|
336
|
-
() => {
|
|
337
|
-
// Load from localStorage or defaults
|
|
338
|
-
const stored = localStorage.getItem('config')
|
|
339
|
-
return stored
|
|
340
|
-
? JSON.parse(stored)
|
|
341
|
-
: { apiUrl: '/api', features: {}, theme: 'light' }
|
|
342
|
-
},
|
|
343
|
-
null // No initial value, will fetch on first access
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
// Load config on app start
|
|
347
|
-
$config.fetch()
|
|
348
|
-
|
|
349
|
-
// Use config throughout app
|
|
350
|
-
effect((t) => {
|
|
351
|
-
const config = $config.get(t)
|
|
352
|
-
if (config) {
|
|
353
|
-
applyTheme(config.theme)
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
### Example 4: File Loading
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
const $selectedFile = state<File | null>(null)
|
|
362
|
-
|
|
363
|
-
const $fileContent = resourceAsync(async () => {
|
|
364
|
-
const file = $selectedFile.pick()
|
|
365
|
-
if (!file) throw new Error('No file selected')
|
|
366
|
-
|
|
367
|
-
return file.text()
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
// Handle file selection
|
|
371
|
-
function handleFileSelect(file: File) {
|
|
372
|
-
$selectedFile.set(file)
|
|
373
|
-
$fileContent.fetch()
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Display content
|
|
377
|
-
effect(async (t) => {
|
|
378
|
-
try {
|
|
379
|
-
const content = await $fileContent.get(t)
|
|
380
|
-
displayContent(content)
|
|
381
|
-
} catch (error) {
|
|
382
|
-
showError('Failed to read file')
|
|
383
|
-
}
|
|
384
|
-
})
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
## Error Handling
|
|
388
|
-
|
|
389
|
-
### Basic Error Handling
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
const $data = resourceAsync(async () => {
|
|
393
|
-
const response = await fetch('/api/data')
|
|
394
|
-
if (!response.ok) {
|
|
395
|
-
throw new Error(`HTTP ${response.status}`)
|
|
396
|
-
}
|
|
397
|
-
return response.json()
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
effect(async (t) => {
|
|
401
|
-
try {
|
|
402
|
-
const data = await $data.get(t)
|
|
403
|
-
displayData(data)
|
|
404
|
-
} catch (error) {
|
|
405
|
-
console.error('Failed to load data:', error)
|
|
406
|
-
showError(error.message)
|
|
407
|
-
}
|
|
408
|
-
})
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### With Error State
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
const $data = resourceAsync(async () => {
|
|
415
|
-
const response = await fetch('/api/data')
|
|
416
|
-
if (!response.ok) throw new Error('Failed to fetch')
|
|
417
|
-
return response.json()
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
const $error = state<string | null>(null)
|
|
421
|
-
const $loading = state(false)
|
|
422
|
-
|
|
423
|
-
async function loadData() {
|
|
424
|
-
$loading.set(true)
|
|
425
|
-
$error.set(null)
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
await $data.fetch()
|
|
429
|
-
} catch (error) {
|
|
430
|
-
$error.set(error.message)
|
|
431
|
-
} finally {
|
|
432
|
-
$loading.set(false)
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
effect(async (t) => {
|
|
437
|
-
const loading = $loading.get(t)
|
|
438
|
-
const error = $error.get(t)
|
|
439
|
-
|
|
440
|
-
if (loading) return showLoading()
|
|
441
|
-
if (error) return showError(error)
|
|
442
|
-
|
|
443
|
-
const data = await $data.get(t)
|
|
444
|
-
showData(data)
|
|
445
|
-
})
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Retry Logic
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
async function fetchWithRetry(
|
|
452
|
-
resourceFn: () => Promise<void>,
|
|
453
|
-
maxRetries = 3
|
|
454
|
-
) {
|
|
455
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
456
|
-
try {
|
|
457
|
-
await resourceFn()
|
|
458
|
-
return // Success!
|
|
459
|
-
} catch (error) {
|
|
460
|
-
if (i === maxRetries - 1) throw error // Last attempt failed
|
|
461
|
-
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Usage
|
|
467
|
-
const $data = resourceAsync(async () => {
|
|
468
|
-
const response = await fetch('/api/data')
|
|
469
|
-
if (!response.ok) throw new Error('Failed to fetch')
|
|
470
|
-
return response.json()
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
fetchWithRetry(() => $data.fetch())
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
## Resource State Transitions
|
|
477
|
-
|
|
478
|
-
Understanding the states a resource goes through:
|
|
479
|
-
|
|
480
|
-
```mermaid
|
|
481
|
-
stateDiagram-v2
|
|
482
|
-
[*] --> Initial: Create resource
|
|
483
|
-
Initial --> Fetching: fetch() called
|
|
484
|
-
Fetching --> Loaded: Success
|
|
485
|
-
Fetching --> Error: Fetch failed
|
|
486
|
-
Loaded --> Fetching: fetch() called
|
|
487
|
-
Error --> Fetching: fetch() called (retry)
|
|
488
|
-
Loaded --> [*]: dispose()
|
|
489
|
-
Error --> [*]: dispose()
|
|
490
|
-
|
|
491
|
-
note right of Initial: value = initial value<br/>or undefined
|
|
492
|
-
note right of Fetching: Promise pending
|
|
493
|
-
note right of Loaded: value = fetched data
|
|
494
|
-
note right of Error: Exception thrown
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
## Best Practices
|
|
498
|
-
|
|
499
|
-
### 1. Provide Initial Values When Possible
|
|
500
|
-
|
|
501
|
-
```typescript
|
|
502
|
-
// ✅ Good - immediate data available
|
|
503
|
-
const $user = resource(
|
|
504
|
-
() => loadUserFromCache(),
|
|
505
|
-
{ id: 0, name: 'Guest' }
|
|
506
|
-
)
|
|
507
|
-
|
|
508
|
-
// ⚠️ Okay but requires undefined check
|
|
509
|
-
const $user = resource(
|
|
510
|
-
() => loadUserFromCache(),
|
|
511
|
-
undefined
|
|
512
|
-
)
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
### 2. Handle Errors Gracefully
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
// ✅ Always handle errors in async resources
|
|
519
|
-
effect(async (t) => {
|
|
520
|
-
try {
|
|
521
|
-
const data = await $resource.get(t)
|
|
522
|
-
showData(data)
|
|
523
|
-
} catch (error) {
|
|
524
|
-
showError(error)
|
|
525
|
-
}
|
|
526
|
-
})
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### 3. Use Loading States
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
const $loading = state(false)
|
|
533
|
-
|
|
534
|
-
async function loadResource() {
|
|
535
|
-
$loading.set(true)
|
|
536
|
-
try {
|
|
537
|
-
await $resource.fetch()
|
|
538
|
-
} finally {
|
|
539
|
-
$loading.set(false)
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### 4. Cleanup on Disposal
|
|
545
|
-
|
|
546
|
-
```typescript
|
|
547
|
-
const $resource = resourceAsync(async () => {
|
|
548
|
-
const controller = new AbortController()
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
const response = await fetch('/api/data', {
|
|
552
|
-
signal: controller.signal
|
|
553
|
-
})
|
|
554
|
-
return response.json()
|
|
555
|
-
} catch (error) {
|
|
556
|
-
if (error.name === 'AbortError') {
|
|
557
|
-
return null
|
|
558
|
-
}
|
|
559
|
-
throw error
|
|
560
|
-
}
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
// Later, dispose to cancel pending requests
|
|
564
|
-
$resource.dispose()
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### 5. Cache Results When Appropriate
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
// Simple cache wrapper
|
|
571
|
-
function cachedResource<T>(
|
|
572
|
-
fetchFn: () => T,
|
|
573
|
-
cacheTime: number
|
|
574
|
-
) {
|
|
575
|
-
let lastFetch = 0
|
|
576
|
-
let cached: T | undefined
|
|
577
|
-
|
|
578
|
-
return resource(
|
|
579
|
-
() => {
|
|
580
|
-
const now = Date.now()
|
|
581
|
-
if (cached && now - lastFetch < cacheTime) {
|
|
582
|
-
return cached // Return cached value
|
|
583
|
-
}
|
|
584
|
-
cached = fetchFn()
|
|
585
|
-
lastFetch = now
|
|
586
|
-
return cached
|
|
587
|
-
},
|
|
588
|
-
undefined
|
|
589
|
-
)
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
const $data = cachedResource(
|
|
593
|
-
() => expensiveFetchOperation(),
|
|
594
|
-
60000 // Cache for 1 minute
|
|
595
|
-
)
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
## Comparing: Resource vs ResourceAsync
|
|
599
|
-
|
|
600
|
-
```mermaid
|
|
601
|
-
flowchart TD
|
|
602
|
-
A{Is fetch function async?} -->|Yes| B[Use resourceAsync]
|
|
603
|
-
A -->|No| C[Use resource]
|
|
604
|
-
B --> D[Returns Promise]
|
|
605
|
-
C --> E[Returns value directly]
|
|
606
|
-
D --> F[Use await in effects]
|
|
607
|
-
E --> G[Use value directly]
|
|
608
|
-
|
|
609
|
-
style B fill:#FFE6E6
|
|
610
|
-
style C fill:#E6FFE6
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
## Complete Example: User Dashboard
|
|
614
|
-
|
|
615
|
-
```typescript
|
|
616
|
-
import { state, resourceAsync, derivation, effect } from '@ersbeth/picoflow'
|
|
617
|
-
|
|
618
|
-
interface User {
|
|
619
|
-
id: number
|
|
620
|
-
name: string
|
|
621
|
-
email: string
|
|
622
|
-
role: string
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
interface Stats {
|
|
626
|
-
loginCount: number
|
|
627
|
-
lastLogin: string
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Current user ID
|
|
631
|
-
const $currentUserId = state<number>(1)
|
|
632
|
-
|
|
633
|
-
// User resource
|
|
634
|
-
const $user = resourceAsync(async () => {
|
|
635
|
-
const userId = $currentUserId.pick()
|
|
636
|
-
const response = await fetch(`/api/users/${userId}`)
|
|
637
|
-
if (!response.ok) throw new Error('User not found')
|
|
638
|
-
return response.json() as Promise<User>
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
// User stats resource
|
|
642
|
-
const $stats = resourceAsync(async () => {
|
|
643
|
-
const userId = $currentUserId.pick()
|
|
644
|
-
const response = await fetch(`/api/users/${userId}/stats`)
|
|
645
|
-
return response.json() as Promise<Stats>
|
|
646
|
-
})
|
|
647
|
-
|
|
648
|
-
// Loading states
|
|
649
|
-
const $loading = state(false)
|
|
650
|
-
const $error = state<string | null>(null)
|
|
651
|
-
|
|
652
|
-
// Derived: is admin
|
|
653
|
-
const $isAdmin = derivation(async (t) => {
|
|
654
|
-
const user = await $user.get(t)
|
|
655
|
-
return user.role === 'admin'
|
|
656
|
-
})
|
|
657
|
-
|
|
658
|
-
// Fetch user data when ID changes
|
|
659
|
-
effect((t) => {
|
|
660
|
-
const userId = $currentUserId.get(t)
|
|
661
|
-
|
|
662
|
-
async function loadUser() {
|
|
663
|
-
$loading.set(true)
|
|
664
|
-
$error.set(null)
|
|
665
|
-
|
|
666
|
-
try {
|
|
667
|
-
await Promise.all([
|
|
668
|
-
$user.fetch(),
|
|
669
|
-
$stats.fetch()
|
|
670
|
-
])
|
|
671
|
-
} catch (error) {
|
|
672
|
-
$error.set(error.message)
|
|
673
|
-
} finally {
|
|
674
|
-
$loading.set(false)
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
loadUser()
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
// Display user info
|
|
682
|
-
effect(async (t) => {
|
|
683
|
-
const loading = $loading.get(t)
|
|
684
|
-
if (loading) return showLoadingSpinner()
|
|
685
|
-
|
|
686
|
-
const error = $error.get(t)
|
|
687
|
-
if (error) return showError(error)
|
|
688
|
-
|
|
689
|
-
try {
|
|
690
|
-
const user = await $user.get(t)
|
|
691
|
-
const stats = await $stats.get(t)
|
|
692
|
-
|
|
693
|
-
displayUserInfo(user, stats)
|
|
694
|
-
} catch (error) {
|
|
695
|
-
showError('Failed to load user data')
|
|
696
|
-
}
|
|
697
|
-
})
|
|
698
|
-
|
|
699
|
-
// Switch user
|
|
700
|
-
function switchUser(newUserId: number) {
|
|
701
|
-
$currentUserId.set(newUserId) // Automatically triggers refetch
|
|
702
|
-
}
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
## Common Patterns
|
|
706
|
-
|
|
707
|
-
### Pattern 1: Dependent Resources
|
|
708
|
-
|
|
709
|
-
```typescript
|
|
710
|
-
// First resource
|
|
711
|
-
const $userId = state(1)
|
|
712
|
-
const $user = resourceAsync(async () => {
|
|
713
|
-
const response = await fetch(`/api/users/${$userId.pick()}`)
|
|
714
|
-
return response.json()
|
|
715
|
-
})
|
|
716
|
-
|
|
717
|
-
// Second resource depends on first
|
|
718
|
-
const $userPosts = resourceAsync(async () => {
|
|
719
|
-
const user = await $user.get(null) // Read without tracking
|
|
720
|
-
const response = await fetch(`/api/users/${user.id}/posts`)
|
|
721
|
-
return response.json()
|
|
722
|
-
})
|
|
723
|
-
|
|
724
|
-
// Fetch both when user changes
|
|
725
|
-
effect((t) => {
|
|
726
|
-
$userId.get(t)
|
|
727
|
-
|
|
728
|
-
async function loadAll() {
|
|
729
|
-
await $user.fetch()
|
|
730
|
-
await $userPosts.fetch() // Fetch posts after user loads
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
loadAll()
|
|
734
|
-
})
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Pattern 2: Polling
|
|
738
|
-
|
|
739
|
-
```typescript
|
|
740
|
-
const $liveData = resourceAsync(async () => {
|
|
741
|
-
const response = await fetch('/api/live-data')
|
|
742
|
-
return response.json()
|
|
743
|
-
})
|
|
744
|
-
|
|
745
|
-
// Poll every 5 seconds
|
|
746
|
-
const pollInterval = setInterval(() => {
|
|
747
|
-
$liveData.fetch()
|
|
748
|
-
}, 5000)
|
|
749
|
-
|
|
750
|
-
// Don't forget cleanup!
|
|
751
|
-
function stopPolling() {
|
|
752
|
-
clearInterval(pollInterval)
|
|
753
|
-
}
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
### Pattern 3: Optimistic Updates
|
|
757
|
-
|
|
758
|
-
```typescript
|
|
759
|
-
const $items = state([{ id: 1, name: 'Item 1' }])
|
|
760
|
-
|
|
761
|
-
async function addItem(name: string) {
|
|
762
|
-
const newItem = { id: Date.now(), name }
|
|
763
|
-
|
|
764
|
-
// Optimistic update
|
|
765
|
-
$items.set(items => [...items, newItem])
|
|
766
|
-
|
|
767
|
-
try {
|
|
768
|
-
await fetch('/api/items', {
|
|
769
|
-
method: 'POST',
|
|
770
|
-
body: JSON.stringify(newItem)
|
|
771
|
-
})
|
|
772
|
-
// Success - keep optimistic update
|
|
773
|
-
} catch (error) {
|
|
774
|
-
// Rollback on error
|
|
775
|
-
$items.set(items => items.filter(i => i.id !== newItem.id))
|
|
776
|
-
showError('Failed to add item')
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
## Best Practices Summary
|
|
782
|
-
|
|
783
|
-
### ✅ Do
|
|
784
|
-
|
|
785
|
-
- Handle errors in async resources
|
|
786
|
-
- Provide initial values when possible
|
|
787
|
-
- Use loading states for better UX
|
|
788
|
-
- Cancel requests on disposal
|
|
789
|
-
- Cache when appropriate
|
|
790
|
-
|
|
791
|
-
### ❌ Don't
|
|
792
|
-
|
|
793
|
-
- Forget error handling
|
|
794
|
-
- Fetch unnecessarily (check if data is still fresh)
|
|
795
|
-
- Create resources inside effects
|
|
796
|
-
- Ignore loading states
|
|
797
|
-
- Leave pending requests on cleanup
|
|
798
|
-
|
|
799
|
-
## Common Pitfalls
|
|
800
|
-
|
|
801
|
-
### Pitfall 1: Not Handling Async Errors
|
|
802
|
-
|
|
803
|
-
```typescript
|
|
804
|
-
// ❌ Unhandled error crashes effect
|
|
805
|
-
effect(async (t) => {
|
|
806
|
-
const data = await $resource.get(t) // Might throw!
|
|
807
|
-
display(data)
|
|
808
|
-
})
|
|
809
|
-
|
|
810
|
-
// ✅ Proper error handling
|
|
811
|
-
effect(async (t) => {
|
|
812
|
-
try {
|
|
813
|
-
const data = await $resource.get(t)
|
|
814
|
-
display(data)
|
|
815
|
-
} catch (error) {
|
|
816
|
-
handleError(error)
|
|
817
|
-
}
|
|
818
|
-
})
|
|
819
|
-
```
|
|
820
|
-
|
|
821
|
-
### Pitfall 2: Forgetting to Call `.fetch()`
|
|
822
|
-
|
|
823
|
-
```typescript
|
|
824
|
-
const $data = resource(() => loadData(), undefined)
|
|
825
|
-
|
|
826
|
-
// ❌ Resource never fetches!
|
|
827
|
-
effect((t) => {
|
|
828
|
-
const data = $data.get(t) // Always undefined
|
|
829
|
-
})
|
|
830
|
-
|
|
831
|
-
// ✅ Trigger fetch
|
|
832
|
-
$data.fetch()
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
### Pitfall 3: Race Conditions
|
|
836
|
-
|
|
837
|
-
```typescript
|
|
838
|
-
const $query = state('initial')
|
|
839
|
-
|
|
840
|
-
const $results = resourceAsync(async () => {
|
|
841
|
-
const query = $query.pick()
|
|
842
|
-
const response = await fetch(`/api/search?q=${query}`)
|
|
843
|
-
return response.json()
|
|
844
|
-
})
|
|
845
|
-
|
|
846
|
-
effect((t) => {
|
|
847
|
-
$query.get(t)
|
|
848
|
-
$results.fetch()
|
|
849
|
-
})
|
|
850
|
-
|
|
851
|
-
// ⚠️ Race condition if user types fast:
|
|
852
|
-
$query.set('a') // Fetch starts for 'a'
|
|
853
|
-
$query.set('ab') // Fetch starts for 'ab'
|
|
854
|
-
$query.set('abc') // Fetch starts for 'abc'
|
|
855
|
-
// Results might arrive out of order!
|
|
856
|
-
|
|
857
|
-
// ✅ Better - use AbortController or check query hasn't changed
|
|
858
|
-
```
|