@ersbeth/picoflow 1.0.0 → 1.1.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/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
- package/README.md +25 -171
- package/biome.json +4 -1
- package/dist/picoflow.js +1129 -661
- package/dist/types/flow/base/flowDisposable.d.ts +67 -0
- package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/flow/base/flowEffect.d.ts +127 -0
- package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
- package/dist/types/flow/base/flowGraph.d.ts +97 -0
- package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
- package/dist/types/flow/base/flowSignal.d.ts +134 -0
- package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
- package/dist/types/flow/base/flowTracker.d.ts +15 -0
- package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
- package/dist/types/flow/base/index.d.ts +7 -0
- package/dist/types/flow/base/index.d.ts.map +1 -0
- package/dist/types/flow/base/utils.d.ts +20 -0
- package/dist/types/flow/base/utils.d.ts.map +1 -0
- package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
- package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
- package/dist/types/flow/collections/flowMap.d.ts +224 -0
- package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
- package/dist/types/flow/collections/index.d.ts +3 -0
- package/dist/types/flow/collections/index.d.ts.map +1 -0
- package/dist/types/flow/index.d.ts +4 -0
- package/dist/types/flow/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/index.d.ts +6 -0
- package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/index.d.ts +3 -0
- package/dist/types/flow/nodes/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/index.d.ts +6 -0
- package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +34 -44
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +1 -0
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +1 -1
- package/docs/api/typedoc-sidebar.json +81 -1
- package/package.json +60 -58
- package/src/flow/base/flowDisposable.ts +71 -0
- package/src/flow/base/flowEffect.ts +171 -0
- package/src/flow/base/flowGraph.ts +288 -0
- package/src/flow/base/flowSignal.ts +207 -0
- package/src/flow/base/flowTracker.ts +17 -0
- package/src/flow/base/index.ts +6 -0
- package/src/flow/base/utils.ts +19 -0
- package/src/flow/collections/flowArray.ts +409 -0
- package/src/flow/collections/flowMap.ts +398 -0
- package/src/flow/collections/index.ts +2 -0
- package/src/flow/index.ts +3 -0
- package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
- package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
- package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
- package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
- package/src/flow/nodes/async/flowStateAsync.ts +116 -0
- package/src/flow/nodes/async/index.ts +5 -0
- package/src/flow/nodes/await/advanced/index.ts +5 -0
- package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
- package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
- package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
- package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
- package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
- package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
- package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
- package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
- package/src/flow/nodes/await/flowStateAwait.ts +130 -0
- package/src/flow/nodes/await/index.ts +5 -0
- package/src/flow/nodes/index.ts +3 -0
- package/src/flow/nodes/sync/flowConstant.ts +111 -0
- package/src/flow/nodes/sync/flowDerivation.ts +105 -0
- package/src/flow/nodes/sync/flowNode.ts +439 -0
- package/src/flow/nodes/sync/flowReadonly.ts +57 -0
- package/src/flow/nodes/sync/flowState.ts +101 -0
- package/src/flow/nodes/sync/index.ts +5 -0
- package/src/index.ts +1 -47
- package/src/solid/converters.ts +59 -198
- package/src/solid/primitives.ts +4 -0
- package/test/base/flowEffect.test.ts +108 -0
- package/test/base/flowGraph.test.ts +485 -0
- package/test/base/flowSignal.test.ts +372 -0
- package/test/collections/flowArray.asyncStates.test.ts +1553 -0
- package/test/collections/flowArray.scalars.test.ts +1129 -0
- package/test/collections/flowArray.states.test.ts +1365 -0
- package/test/collections/flowMap.asyncStates.test.ts +1105 -0
- package/test/collections/flowMap.scalars.test.ts +877 -0
- package/test/collections/flowMap.states.test.ts +1097 -0
- package/test/nodes/async/flowConstantAsync.test.ts +860 -0
- package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
- package/test/nodes/async/flowStateAsync.test.ts +1387 -0
- package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
- package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
- package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
- package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
- package/test/nodes/await/flowConstantAwait.test.ts +643 -0
- package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
- package/test/nodes/await/flowStateAwait.test.ts +999 -0
- package/test/nodes/mixed/derivation.test.ts +1527 -0
- package/test/nodes/sync/flowConstant.test.ts +620 -0
- package/test/nodes/sync/flowDerivation.test.ts +1373 -0
- package/test/nodes/sync/flowState.test.ts +945 -0
- package/test/solid/converters.test.ts +721 -0
- package/test/solid/primitives.test.ts +1031 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +7 -1
- package/IMPLEMENTATION_GUIDE.md +0 -1578
- package/dist/types/advanced/array.d.ts.map +0 -1
- package/dist/types/advanced/index.d.ts +0 -9
- package/dist/types/advanced/index.d.ts.map +0 -1
- package/dist/types/advanced/map.d.ts +0 -166
- package/dist/types/advanced/map.d.ts.map +0 -1
- package/dist/types/advanced/resource.d.ts +0 -78
- package/dist/types/advanced/resource.d.ts.map +0 -1
- package/dist/types/advanced/resourceAsync.d.ts +0 -56
- package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
- package/dist/types/advanced/stream.d.ts +0 -117
- package/dist/types/advanced/stream.d.ts.map +0 -1
- package/dist/types/advanced/streamAsync.d.ts +0 -97
- package/dist/types/advanced/streamAsync.d.ts.map +0 -1
- package/dist/types/basic/constant.d.ts +0 -60
- package/dist/types/basic/constant.d.ts.map +0 -1
- package/dist/types/basic/derivation.d.ts +0 -89
- package/dist/types/basic/derivation.d.ts.map +0 -1
- package/dist/types/basic/disposable.d.ts +0 -82
- package/dist/types/basic/disposable.d.ts.map +0 -1
- package/dist/types/basic/effect.d.ts +0 -67
- package/dist/types/basic/effect.d.ts.map +0 -1
- package/dist/types/basic/index.d.ts +0 -10
- package/dist/types/basic/index.d.ts.map +0 -1
- package/dist/types/basic/observable.d.ts +0 -83
- package/dist/types/basic/observable.d.ts.map +0 -1
- package/dist/types/basic/signal.d.ts +0 -69
- package/dist/types/basic/signal.d.ts.map +0 -1
- package/dist/types/basic/state.d.ts +0 -47
- package/dist/types/basic/state.d.ts.map +0 -1
- package/dist/types/basic/trackingContext.d.ts +0 -33
- package/dist/types/basic/trackingContext.d.ts.map +0 -1
- package/dist/types/creators.d.ts +0 -340
- package/dist/types/creators.d.ts.map +0 -1
- package/src/advanced/array.ts +0 -222
- package/src/advanced/index.ts +0 -12
- package/src/advanced/map.ts +0 -193
- package/src/basic/constant.ts +0 -97
- package/src/basic/derivation.ts +0 -147
- package/src/basic/disposable.ts +0 -86
- package/src/basic/effect.ts +0 -104
- package/src/basic/index.ts +0 -9
- package/src/basic/observable.ts +0 -109
- package/src/basic/signal.ts +0 -145
- package/src/basic/state.ts +0 -60
- package/src/basic/trackingContext.ts +0 -45
- package/src/creators.ts +0 -395
- package/test/array.test.ts +0 -600
- package/test/constant.test.ts +0 -44
- package/test/derivation.test.ts +0 -539
- package/test/effect.test.ts +0 -29
- package/test/map.test.ts +0 -240
- package/test/signal.test.ts +0 -72
- package/test/state.test.ts +0 -212
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
<!-- c9506e24-37d6-4d95-a937-545ccc76fe61 744135bd-5bf9-4a27-9006-1c9cfd4392d8 -->
|
|
2
|
+
# Plan d'implémentation : Unification FlowResource avec FlowDerivation (approche intégrée)
|
|
3
|
+
|
|
4
|
+
## 1. Modifications d'API
|
|
5
|
+
|
|
6
|
+
### 1.1 FlowDerivation - Option `synchrone` et méthode `refresh()`
|
|
7
|
+
|
|
8
|
+
**Fichier**: `src/basic/derivation.ts`
|
|
9
|
+
|
|
10
|
+
**Modifications du constructeur**:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
constructor(
|
|
14
|
+
compute: (t: TrackingContext) => T | Promise<T>,
|
|
15
|
+
options?: {
|
|
16
|
+
synchrone?: boolean; // Si true avec fonction async, retourne T | undefined
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Nouvelle méthode**:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
public refresh(): void
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Comportement avec `synchrone: true`**:
|
|
28
|
+
|
|
29
|
+
- Quand `synchrone: true` est fourni avec une fonction async (`Promise<T>`):
|
|
30
|
+
- Le type de retour devient `FlowDerivation<T | undefined>` au lieu de `FlowDerivation<Promise<T>>`
|
|
31
|
+
- Retourne `undefined` initialement, puis `T` une fois la Promise résolue
|
|
32
|
+
- Compare les valeurs avec `===` avant de notifier (évite notifications inutiles)
|
|
33
|
+
- Gère les erreurs de Promise (throw)
|
|
34
|
+
- Quand `synchrone: true` est fourni avec une fonction synchrone:
|
|
35
|
+
- Erreur de type TypeScript (incompatibilité de types)
|
|
36
|
+
- `refresh()` marque comme dirty et notifie (même comportement que sans option)
|
|
37
|
+
|
|
38
|
+
**Usage**:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// Dérivation async normale
|
|
42
|
+
const $userAsync = derivation(async (t) => await fetchUser());
|
|
43
|
+
const userPromise = $userAsync.pick(); // Promise<User>
|
|
44
|
+
|
|
45
|
+
// Dérivation async synchronisée
|
|
46
|
+
const $user = derivation(async (t) => await fetchUser(), { synchrone: true });
|
|
47
|
+
const user = $user.pick(); // User | undefined
|
|
48
|
+
|
|
49
|
+
// Refresh
|
|
50
|
+
$user.refresh(); // Marque comme dirty
|
|
51
|
+
await $user.pick(); // Déclenche le recalcul et attend
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 1.2 Suppression de FlowSynchronizer
|
|
55
|
+
|
|
56
|
+
**Changement d'approche**:
|
|
57
|
+
|
|
58
|
+
- Plus besoin de classe `FlowSynchronizer` séparée
|
|
59
|
+
- La synchronisation est intégrée directement dans `FlowDerivation`
|
|
60
|
+
|
|
61
|
+
### 1.3 Modifications des creators
|
|
62
|
+
|
|
63
|
+
**Fichier**: `src/creators.ts`
|
|
64
|
+
|
|
65
|
+
**Changements**:
|
|
66
|
+
|
|
67
|
+
1. **`resourceAsync()`** - Simplifié:
|
|
68
|
+
```typescript
|
|
69
|
+
// Avant: retourne FlowResourceAsync
|
|
70
|
+
// Après: retourne FlowDerivation<Promise<T>>
|
|
71
|
+
export function resourceAsync<T>(fn: () => Promise<T>): FlowDerivation<Promise<T>> {
|
|
72
|
+
return derivation(() => fn());
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
2. **`resource()`** - Utilise l'option `synchrone`:
|
|
77
|
+
```typescript
|
|
78
|
+
// Avant: retourne FlowResource
|
|
79
|
+
// Après: retourne FlowDerivation<T | undefined>
|
|
80
|
+
export function resource<T>(fn: () => Promise<T>): FlowDerivation<T | undefined> {
|
|
81
|
+
return derivation(() => fn(), { synchrone: true });
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### 1.4 Exports
|
|
87
|
+
|
|
88
|
+
**Fichiers**: `src/advanced/index.ts`, `src/index.ts`
|
|
89
|
+
|
|
90
|
+
**Suppressions** (après migration):
|
|
91
|
+
|
|
92
|
+
- `FlowResource` (remplacé par `FlowDerivation` avec `synchrone: true`)
|
|
93
|
+
- `FlowResourceAsync` (remplacé par `FlowDerivation<Promise<T>>`)
|
|
94
|
+
- Plus besoin d'exporter `FlowSynchronizer`
|
|
95
|
+
|
|
96
|
+
### 1.5 Rétrocompatibilité
|
|
97
|
+
|
|
98
|
+
**Impact**:
|
|
99
|
+
|
|
100
|
+
- Les types de retour changent, mais l'API reste compatible fonctionnellement
|
|
101
|
+
- `resource()` et `resourceAsync()` continuent de fonctionner de la même manière
|
|
102
|
+
- Les tests existants doivent continuer de passer (avec ajustements de types si nécessaire)
|
|
103
|
+
|
|
104
|
+
## 2. Implémentations détaillées
|
|
105
|
+
|
|
106
|
+
### 2.1 FlowDerivation - Support de l'option `synchrone`
|
|
107
|
+
|
|
108
|
+
**Fichier**: `src/basic/derivation.ts`
|
|
109
|
+
|
|
110
|
+
**Modifications du constructeur**:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
constructor(
|
|
114
|
+
compute: (t: TrackingContext) => T | Promise<T>,
|
|
115
|
+
options?: { synchrone?: boolean }
|
|
116
|
+
) {
|
|
117
|
+
super();
|
|
118
|
+
this._compute = compute;
|
|
119
|
+
this._trackedContext = new TrackingContext(this);
|
|
120
|
+
this._synchrone = options?.synchrone ?? false;
|
|
121
|
+
|
|
122
|
+
// Si synchrone est activé, on doit gérer les Promises différemment
|
|
123
|
+
if (this._synchrone) {
|
|
124
|
+
// Initialiser la valeur à undefined pour le mode synchrone
|
|
125
|
+
this._value = undefined as T;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Modifications de `_getRaw()`**:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
protected _getRaw(): T {
|
|
134
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
135
|
+
|
|
136
|
+
this._initLazy();
|
|
137
|
+
this._update();
|
|
138
|
+
|
|
139
|
+
if (this._synchrone) {
|
|
140
|
+
// En mode synchrone, la valeur peut être undefined si la Promise n'est pas encore résolue
|
|
141
|
+
return this._value as T;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return this._value;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Modifications de `_update()` pour gérer la synchronisation**:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
private _update(): void {
|
|
152
|
+
if (this._dirty) {
|
|
153
|
+
// Store current dependencies
|
|
154
|
+
const dependencies = [...this._dependencies];
|
|
155
|
+
|
|
156
|
+
// Clear current dependencies, compute and retrack dependencies
|
|
157
|
+
this._dependencies.clear();
|
|
158
|
+
const computedValue = this._compute(this._trackedContext);
|
|
159
|
+
|
|
160
|
+
if (this._synchrone && computedValue instanceof Promise) {
|
|
161
|
+
// Mode synchrone avec Promise
|
|
162
|
+
this._handleSyncPromise(computedValue as Promise<T>);
|
|
163
|
+
} else {
|
|
164
|
+
// Mode normal
|
|
165
|
+
this._value = computedValue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Unsubscribe from dependencies that are no longer needed
|
|
169
|
+
const dependenciesToRemove = dependencies.filter(
|
|
170
|
+
(dependency) => !this._dependencies.has(dependency),
|
|
171
|
+
);
|
|
172
|
+
dependenciesToRemove.forEach((dependency) => {
|
|
173
|
+
dependency._unregisterDependency(this);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this._dirty = false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Nouvelle méthode `_handleSyncPromise()`**:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
private async _handleSyncPromise(promise: Promise<T>): Promise<void> {
|
|
185
|
+
// Marquer cette Promise comme la Promise courante
|
|
186
|
+
this._currentPromise = promise;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const value = await promise;
|
|
190
|
+
|
|
191
|
+
// Vérifier que c'est toujours la Promise courante (éviter les race conditions)
|
|
192
|
+
if (this._currentPromise !== promise) {
|
|
193
|
+
return; // Une nouvelle Promise a été créée, ignorer cette résolution
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Comparer avec la valeur actuelle pour éviter les notifications inutiles
|
|
197
|
+
if (value === this._value) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Mettre à jour la valeur et notifier
|
|
202
|
+
this._value = value as T;
|
|
203
|
+
this._notify();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
// Vérifier que c'est toujours la Promise courante
|
|
206
|
+
if (this._currentPromise !== promise) {
|
|
207
|
+
return; // Une nouvelle Promise a été créée, ignorer cette erreur
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Throw l'erreur
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Nouvelle méthode `refresh()`**:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
public refresh(): void {
|
|
220
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
221
|
+
|
|
222
|
+
// Marquer comme dirty (le recalcul se fera au prochain accès)
|
|
223
|
+
this._dirty = true;
|
|
224
|
+
|
|
225
|
+
// Notifier les dépendants pour qu'ils se marquent aussi comme dirty
|
|
226
|
+
this._notify();
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Nouveaux champs privés**:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
private _synchrone: boolean;
|
|
234
|
+
private _currentPromise?: Promise<T>;
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Points d'attention**:
|
|
238
|
+
|
|
239
|
+
1. **Type safety**: Le type de retour doit être `FlowDerivation<T | undefined>` quand `synchrone: true` avec une fonction async
|
|
240
|
+
2. **Gestion des Promises**: En mode synchrone, les Promises sont gérées de manière asynchrone dans `_handleSyncPromise()`
|
|
241
|
+
3. **Comparaison de valeurs**: Utiliser `===` uniquement en mode synchrone
|
|
242
|
+
4. **Race conditions**: Utiliser `_currentPromise` pour tracker la Promise courante
|
|
243
|
+
5. **Initialisation**: En mode synchrone, initialiser `_value` à `undefined`
|
|
244
|
+
6. **Lazy evaluation**: Le comportement lazy reste le même, mais la résolution de Promise est asynchrone
|
|
245
|
+
|
|
246
|
+
### 2.2 Mise à jour des creators
|
|
247
|
+
|
|
248
|
+
**Fichier**: `src/creators.ts`
|
|
249
|
+
|
|
250
|
+
**Implémentation `resourceAsync()`**:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
export function resourceAsync<T>(fn: () => Promise<T>): FlowDerivation<Promise<T>> {
|
|
254
|
+
return derivation(() => fn());
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Points d'attention**:
|
|
259
|
+
|
|
260
|
+
- La fonction `fn` ne reçoit pas de `TrackingContext` (comportement actuel)
|
|
261
|
+
- Retourne directement une `FlowDerivation<Promise<T>>`
|
|
262
|
+
|
|
263
|
+
**Implémentation `resource()`**:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
export function resource<T>(fn: () => Promise<T>): FlowDerivation<T | undefined> {
|
|
267
|
+
return derivation(() => fn(), { synchrone: true });
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Points d'attention**:
|
|
272
|
+
|
|
273
|
+
- Utilise l'option `synchrone: true` pour activer le mode synchrone
|
|
274
|
+
- Retourne `FlowDerivation<T | undefined>`
|
|
275
|
+
|
|
276
|
+
### 2.3 Mise à jour des exports
|
|
277
|
+
|
|
278
|
+
**Fichier**: `src/advanced/index.ts`
|
|
279
|
+
|
|
280
|
+
**Modifications**:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Supprimer (après migration complète)
|
|
284
|
+
// export { FlowResource } from "./resource";
|
|
285
|
+
// export { FlowResourceAsync } from "./resourceAsync";
|
|
286
|
+
// Plus besoin de FlowSynchronizer
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Fichier**: `src/index.ts`
|
|
290
|
+
|
|
291
|
+
**Modifications**:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Supprimer FlowResource et FlowResourceAsync des exports
|
|
295
|
+
export {
|
|
296
|
+
FlowArray,
|
|
297
|
+
type FlowArrayAction,
|
|
298
|
+
FlowMap,
|
|
299
|
+
FlowStream,
|
|
300
|
+
FlowStreamAsync,
|
|
301
|
+
// FlowResource, // À supprimer
|
|
302
|
+
// FlowResourceAsync, // À supprimer
|
|
303
|
+
...
|
|
304
|
+
} from "./advanced/";
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 2.4 Migration des tests
|
|
308
|
+
|
|
309
|
+
**Fichiers**: `test/resource.test.ts`, `test/resourceAsync.test.ts`
|
|
310
|
+
|
|
311
|
+
**Actions**:
|
|
312
|
+
|
|
313
|
+
1. Mettre à jour les imports pour utiliser `FlowDerivation`
|
|
314
|
+
2. Adapter les types de retour des fonctions `resource()` et `resourceAsync()`
|
|
315
|
+
3. Adapter les appels à `refresh()` : `resource.refresh()` puis `await resource.pick()` si nécessaire
|
|
316
|
+
4. Vérifier que tous les tests passent avec la nouvelle implémentation
|
|
317
|
+
5. Potentiellement fusionner les tests si la logique est similaire
|
|
318
|
+
|
|
319
|
+
**Points d'attention**:
|
|
320
|
+
|
|
321
|
+
- Les tests de `resource()` doivent maintenant tester `FlowDerivation<T | undefined>` avec `synchrone: true`
|
|
322
|
+
- Les tests de `resourceAsync()` doivent tester `FlowDerivation<Promise<T>>`
|
|
323
|
+
- Vérifier que le comportement lazy est toujours correct
|
|
324
|
+
- Vérifier que la comparaison de valeurs fonctionne en mode synchrone
|
|
325
|
+
- Vérifier que les race conditions sont gérées correctement
|
|
326
|
+
|
|
327
|
+
### 2.5 Suppression des anciennes classes
|
|
328
|
+
|
|
329
|
+
**Fichiers**: `src/advanced/resource.ts`, `src/advanced/resourceAsync.ts`
|
|
330
|
+
|
|
331
|
+
**Actions** (après vérification que tout fonctionne):
|
|
332
|
+
|
|
333
|
+
1. Supprimer `src/advanced/resource.ts`
|
|
334
|
+
2. Supprimer `src/advanced/resourceAsync.ts`
|
|
335
|
+
3. Nettoyer les exports dans `src/advanced/index.ts`
|
|
336
|
+
|
|
337
|
+
**Points d'attention**:
|
|
338
|
+
|
|
339
|
+
- Ne supprimer qu'après avoir vérifié que tous les tests passent
|
|
340
|
+
- Vérifier qu'aucun code externe n'importe directement ces classes
|
|
341
|
+
- Mettre à jour la documentation si nécessaire
|
|
342
|
+
|
|
343
|
+
## 3. Ordre d'implémentation recommandé
|
|
344
|
+
|
|
345
|
+
1. **Étape 1**: Ajouter l'option `synchrone` et la méthode `refresh()` à `FlowDerivation` et tester
|
|
346
|
+
2. **Étape 2**: Implémenter la gestion des Promises en mode synchrone avec comparaison de valeurs
|
|
347
|
+
3. **Étape 3**: Mettre à jour les creators `resource()` et `resourceAsync()`
|
|
348
|
+
4. **Étape 4**: Mettre à jour les exports
|
|
349
|
+
5. **Étape 5**: Migrer et adapter les tests
|
|
350
|
+
6. **Étape 6**: Supprimer les anciennes classes `FlowResource` et `FlowResourceAsync`
|
|
351
|
+
|
|
352
|
+
## 4. Cas limites à tester
|
|
353
|
+
|
|
354
|
+
1. **Race conditions**: Appels multiples rapides à `refresh()` avec mode synchrone
|
|
355
|
+
2. **Erreurs async**: Promises rejetées dans les derivations en mode synchrone
|
|
356
|
+
3. **Lazy initialization**: Vérifier que le fetch ne se fait qu'au premier accès
|
|
357
|
+
4. **Comparaison de valeurs**: Vérifier que les notifications ne se déclenchent que si la valeur change (mode synchrone uniquement)
|
|
358
|
+
5. **Disposal**: Vérifier le cleanup correct
|
|
359
|
+
6. **Dérivations sync avec refresh()**: Vérifier que ça fonctionne aussi pour les derivations non-async
|
|
360
|
+
7. **Refresh puis accès**: Vérifier que `refresh()` puis `pick()`/`get()` déclenche bien le recalcul
|
|
361
|
+
8. **Refresh sur dérivation non-initialisée**: Vérifier que `refresh()` fonctionne même si la dérivation n'a pas encore été accédée
|
|
362
|
+
9. **Mode synchrone avec fonction sync**: Vérifier que TypeScript lève une erreur de type
|
|
363
|
+
10. **Plusieurs Promises successives**: Vérifier que seule la dernière Promise résolue met à jour la valeur
|
|
364
|
+
|
|
365
|
+
### To-dos
|
|
366
|
+
|
|
367
|
+
- [ ] Ajouter la méthode refresh() à FlowDerivation dans src/basic/derivation.ts avec gestion des derivations sync et async
|
|
368
|
+
- [ ] Créer la classe FlowSynchronizer dans src/advanced/synchronizer.ts avec lazy initialization, gestion des race conditions, et comparaison de valeurs
|
|
369
|
+
- [ ] Mettre à jour resource() et resourceAsync() dans src/creators.ts pour utiliser FlowDerivation et FlowSynchronizer
|
|
370
|
+
- [ ] Mettre à jour les exports dans src/advanced/index.ts et src/index.ts pour exporter FlowSynchronizer et supprimer FlowResource/FlowResourceAsync
|
|
371
|
+
- [ ] Adapter les tests dans test/resource.test.ts et test/resourceAsync.test.ts pour la nouvelle API
|
|
372
|
+
- [ ] Supprimer src/advanced/resource.ts et src/advanced/resourceAsync.ts après vérification
|
package/README.md
CHANGED
|
@@ -1,188 +1,42 @@
|
|
|
1
1
|
# Picoflow
|
|
2
2
|
|
|
3
|
-
**Picoflow** is a lightweight reactive dataflow library that provides fine-grained reactive primitives. It gives you an intuitive API for signals, state, asynchronous
|
|
3
|
+
**Picoflow** is a lightweight reactive dataflow library that provides fine-grained reactive primitives. It gives you an intuitive API for signals, state, (asynchronous) derivations, effects, and reactive maps/arrays. Picoflow uses an explicit tracking context to automatically track reactive dependencies.
|
|
4
4
|
|
|
5
|
-
> **Upgrading from v0.x?** See the [Upgrade Guide](
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Reactive Signals:** Manually trigger updates using `$signal.trigger()`.
|
|
10
|
-
- **Reactive State:** Manage state with observables that expose getter/setter APIs.
|
|
11
|
-
- **Asynchronous Resources:** Fetch and update asynchronous data reactively.
|
|
12
|
-
- **Reactive Streams:** Create streams that update over time using an updater function.
|
|
13
|
-
- **Derivations:** Compute derived values from other reactive primitives.
|
|
14
|
-
- **Automatic Caching:** Derived values are cached and are re-evaluated only when one or more of their dependencies change.
|
|
15
|
-
- **Lazy Evaluation:** Derivations are evaluated lazily—that is, the computation only runs if and when an effect actually uses the derived value.
|
|
16
|
-
- **Reactive Maps:** Manage collections reactively with granular update notifications.
|
|
17
|
-
- **Effects:** Automatically run side-effect functions when dependencies change.
|
|
18
|
-
- **Explicit Control:** Choose between reactive (`.get(t)`) and non-reactive (`.pick()`) reads for fine-grained control.
|
|
5
|
+
> **Upgrading from v0.x?** See the [Upgrade Guide](https://ersbeth-web.gitlab.io/picoflow/guide/advanced/upgrading.html) for migration instructions.
|
|
19
6
|
|
|
20
7
|
## Installation
|
|
21
8
|
|
|
22
|
-
Install via npm:
|
|
23
|
-
|
|
24
9
|
```bash
|
|
10
|
+
# npm
|
|
25
11
|
npm install @ersbeth/picoflow
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Usage
|
|
29
|
-
|
|
30
|
-
In Picoflow, we advise to prefix all reactive primitives and effects with `$` for improved readability. Rather than using a subscribe method, you always create an effect that tracks dependencies using a tracking context. Below are some examples:
|
|
31
|
-
|
|
32
|
-
### Creating a Signal
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import { signal, effect } from '@ersbeth/picoflow';
|
|
36
|
-
|
|
37
|
-
const $signal = signal();
|
|
38
|
-
|
|
39
|
-
const $effect = effect((t) => {
|
|
40
|
-
// Automatically tracks $signal
|
|
41
|
-
$signal.watch(t);
|
|
42
|
-
console.log('$signal has been triggered');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Trigger the signal
|
|
46
|
-
$signal.trigger();
|
|
47
|
-
|
|
48
|
-
// LOG -> $signal has been triggered
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Creating Reactive State
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import { state, effect } from '@ersbeth/picoflow';
|
|
55
|
-
|
|
56
|
-
const $count = state(0);
|
|
57
|
-
|
|
58
|
-
const $effect = effect((t) => {
|
|
59
|
-
// Automatically tracks $count
|
|
60
|
-
console.log('Count changed:', $count.get(t));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Update the state
|
|
64
|
-
$count.set(42);
|
|
65
|
-
|
|
66
|
-
// LOG -> Count changed: 42
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Creating an Asynchronous Resource
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { resource, effect } from '@ersbeth/picoflow';
|
|
73
|
-
|
|
74
|
-
async function fetchData() {
|
|
75
|
-
const response = await fetch('https://api.example.com/data');
|
|
76
|
-
return response.json();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const $data = resource(fetchData, {});
|
|
80
|
-
|
|
81
|
-
// Create an effect to watch for updates
|
|
82
|
-
const $effect = effect((t) => {
|
|
83
|
-
console.log('Resource updated:', $data.get(t));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Later, you can trigger a new fetch
|
|
87
|
-
$data.fetch();
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Creating a Reactive Stream
|
|
91
12
|
|
|
92
|
-
|
|
93
|
-
|
|
13
|
+
# pnpm
|
|
14
|
+
pnpm add @ersbeth/picoflow
|
|
94
15
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const interval = setInterval(() => set(Date.now()), 1000);
|
|
98
|
-
// Return a disposer to clear the interval when the stream is disposed.
|
|
99
|
-
return () => clearInterval(interval);
|
|
100
|
-
},
|
|
101
|
-
Date.now()
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const $effect = effect((t) => {
|
|
105
|
-
console.log('Current time:', $ticker.get(t));
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Creating a Derived Value
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
import { state, derivation, effect } from '@ersbeth/picoflow';
|
|
113
|
-
|
|
114
|
-
const $a = state(10);
|
|
115
|
-
const $b = state(20);
|
|
116
|
-
|
|
117
|
-
// Create a derived value that sums $a and $b.
|
|
118
|
-
const $sum = derivation((t) => {
|
|
119
|
-
return $a.get(t) + $b.get(t);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const $effect = effect((t) => {
|
|
123
|
-
console.log('Sum:', $sum.get(t));
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Update a dependency to see the derivation update.
|
|
127
|
-
$a.set(15);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Creating a Reactive Map
|
|
131
|
-
|
|
132
|
-
Reactive maps allow fine-grained tracking of collection updates.
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
import { map, effect } from '@ersbeth/picoflow';
|
|
136
|
-
|
|
137
|
-
const $map = map({ foo: 'bar' });
|
|
138
|
-
|
|
139
|
-
const $effect = effect((t) => {
|
|
140
|
-
console.log('Map updated:', $map.get(t));
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Update the map to trigger updates
|
|
144
|
-
$map.setAt('baz', 'qux');
|
|
145
|
-
$map.delete('foo');
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Non-Reactive Reads
|
|
149
|
-
|
|
150
|
-
Sometimes you need to read a value without creating a dependency. Use `pick()` for this:
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
import { state, signal, effect } from '@ersbeth/picoflow';
|
|
154
|
-
|
|
155
|
-
const $data = state({ count: 0 });
|
|
156
|
-
const $trigger = signal();
|
|
157
|
-
|
|
158
|
-
const $effect = effect((t) => {
|
|
159
|
-
// React to $trigger changes only
|
|
160
|
-
$trigger.watch(t);
|
|
161
|
-
|
|
162
|
-
// Read $data without creating a dependency
|
|
163
|
-
const snapshot = $data.pick();
|
|
164
|
-
console.log('Triggered! Current data:', snapshot);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
$data.set({ count: 1 }); // Does NOT trigger the effect
|
|
168
|
-
$trigger.trigger(); // Triggers the effect, logs: { count: 1 }
|
|
16
|
+
# yarn
|
|
17
|
+
yarn add @ersbeth/picoflow
|
|
169
18
|
```
|
|
170
19
|
|
|
171
20
|
## Documentation
|
|
172
21
|
|
|
173
|
-
For comprehensive guides and API documentation, visit
|
|
174
|
-
|
|
175
|
-
- **[Getting Started](docs/guide/getting-started.md)** - Quick introduction to PicoFlow
|
|
176
|
-
- **[API Reference](docs/api/index.md)** - Complete API documentation
|
|
177
|
-
|
|
178
|
-
Or run the documentation locally:
|
|
179
|
-
|
|
180
|
-
```bash
|
|
181
|
-
pnpm docs:dev
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Then open [http://localhost:5173](http://localhost:5173) in your browser.
|
|
22
|
+
For comprehensive guides and API documentation, visit the [official website](https://ersbeth-web.gitlab.io/picoflow/)
|
|
185
23
|
|
|
186
24
|
## License
|
|
187
25
|
|
|
188
|
-
This project is licensed under the [MIT License](LICENSE).
|
|
26
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
27
|
+
|
|
28
|
+
## TODO
|
|
29
|
+
|
|
30
|
+
- [x] implement refresh on derivations
|
|
31
|
+
- [x] add tests for refresh
|
|
32
|
+
- [x] improve tests for array / maps
|
|
33
|
+
- [x] use same API for array and maps
|
|
34
|
+
- [x] use async API for array updates
|
|
35
|
+
- [x] manage unified disposal in collections
|
|
36
|
+
- [ ] fix tests
|
|
37
|
+
- [ ] implement sets
|
|
38
|
+
- [x] implement await nodes
|
|
39
|
+
- [ ] add tests for await nodes
|
|
40
|
+
- [ ] add tests for raw nodes
|
|
41
|
+
- [ ] replace resources by await nodes
|
|
42
|
+
- [ ] think about streams
|