@ersbeth/picoflow 0.0.1 → 0.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.
Files changed (143) hide show
  1. package/api/doc/index.md +1 -1
  2. package/api/doc/picoflow.constant.md +55 -0
  3. package/api/doc/picoflow.derivation.md +1 -1
  4. package/api/doc/picoflow.effect.md +1 -1
  5. package/api/doc/picoflow.flowconstant._constructor_.md +49 -0
  6. package/api/doc/picoflow.flowconstant.get.md +25 -0
  7. package/api/doc/picoflow.flowconstant.md +88 -0
  8. package/api/doc/picoflow.flowderivation._constructor_.md +2 -2
  9. package/api/doc/picoflow.flowderivation.get.md +2 -2
  10. package/api/doc/picoflow.flowderivation.md +2 -2
  11. package/api/doc/picoflow.floweffect._constructor_.md +7 -2
  12. package/api/doc/picoflow.floweffect.dispose.md +3 -3
  13. package/api/doc/picoflow.floweffect.disposed.md +1 -1
  14. package/api/doc/picoflow.floweffect.md +4 -4
  15. package/api/doc/picoflow.flowgetter.md +2 -2
  16. package/api/doc/picoflow.flowmap._lastdeleted.md +1 -1
  17. package/api/doc/picoflow.flowmap._lastset.md +1 -1
  18. package/api/doc/picoflow.flowmap.delete.md +6 -2
  19. package/api/doc/picoflow.flowmap.md +5 -7
  20. package/api/doc/picoflow.flowmap.setat.md +6 -2
  21. package/api/doc/picoflow.flowobservable.get.md +3 -3
  22. package/api/doc/picoflow.flowobservable.md +18 -4
  23. package/api/doc/picoflow.flowobservable.subscribe.md +55 -0
  24. package/api/doc/picoflow.flowresource._constructor_.md +2 -18
  25. package/api/doc/picoflow.flowresource.fetch.md +1 -1
  26. package/api/doc/picoflow.flowresource.get.md +4 -4
  27. package/api/doc/picoflow.flowresource.md +4 -4
  28. package/api/doc/picoflow.flowresourceasync._constructor_.md +49 -0
  29. package/api/doc/picoflow.flowresourceasync.fetch.md +27 -0
  30. package/api/doc/picoflow.flowresourceasync.get.md +23 -0
  31. package/api/doc/picoflow.flowresourceasync.md +100 -0
  32. package/api/doc/picoflow.flowsignal.dispose.md +3 -7
  33. package/api/doc/picoflow.flowsignal.disposed.md +2 -2
  34. package/api/doc/picoflow.flowsignal.md +5 -5
  35. package/api/doc/picoflow.flowsignal.trigger.md +3 -7
  36. package/api/doc/picoflow.flowstate.md +4 -52
  37. package/api/doc/picoflow.flowstate.set.md +5 -5
  38. package/api/doc/picoflow.flowstream._constructor_.md +3 -19
  39. package/api/doc/picoflow.flowstream.dispose.md +1 -1
  40. package/api/doc/picoflow.flowstream.get.md +4 -4
  41. package/api/doc/picoflow.flowstream.md +5 -5
  42. package/api/doc/picoflow.flowstreamasync._constructor_.md +54 -0
  43. package/api/doc/picoflow.flowstreamasync.dispose.md +21 -0
  44. package/api/doc/picoflow.flowstreamasync.get.md +23 -0
  45. package/api/doc/picoflow.flowstreamasync.md +100 -0
  46. package/api/doc/picoflow.flowstreamdisposer.md +13 -0
  47. package/api/doc/picoflow.flowstreamsetter.md +13 -0
  48. package/api/doc/picoflow.flowstreamupdater.md +19 -0
  49. package/api/doc/picoflow.flowwatcher.md +1 -1
  50. package/api/doc/picoflow.map.md +1 -1
  51. package/api/doc/picoflow.md +80 -14
  52. package/api/doc/picoflow.resource.md +2 -18
  53. package/api/doc/picoflow.resourceasync.md +55 -0
  54. package/api/doc/picoflow.signal.md +1 -1
  55. package/api/doc/picoflow.state.md +3 -3
  56. package/api/doc/picoflow.stream.md +2 -18
  57. package/api/doc/picoflow.streamasync.md +55 -0
  58. package/api/picoflow.public.api.md +131 -0
  59. package/api-extractor.json +2 -1
  60. package/dist/picoflow.js +326 -302
  61. package/dist/types/advanced/index.d.ts +7 -0
  62. package/dist/types/advanced/index.d.ts.map +1 -0
  63. package/dist/types/{map.d.ts → advanced/map.d.ts} +12 -12
  64. package/dist/types/advanced/map.d.ts.map +1 -0
  65. package/dist/types/advanced/resource.d.ts +39 -0
  66. package/dist/types/advanced/resource.d.ts.map +1 -0
  67. package/dist/types/{resource.d.ts → advanced/resourceAsync.d.ts} +6 -11
  68. package/dist/types/advanced/resourceAsync.d.ts.map +1 -0
  69. package/dist/types/advanced/stream.d.ts +59 -0
  70. package/dist/types/advanced/stream.d.ts.map +1 -0
  71. package/dist/types/advanced/streamAsync.d.ts +43 -0
  72. package/dist/types/advanced/streamAsync.d.ts.map +1 -0
  73. package/dist/types/basic/constant.d.ts +32 -0
  74. package/dist/types/basic/constant.d.ts.map +1 -0
  75. package/dist/types/basic/derivation.d.ts +40 -0
  76. package/dist/types/basic/derivation.d.ts.map +1 -0
  77. package/dist/types/basic/effect.d.ts +56 -0
  78. package/dist/types/basic/effect.d.ts.map +1 -0
  79. package/dist/types/basic/index.d.ts +9 -0
  80. package/dist/types/basic/index.d.ts.map +1 -0
  81. package/dist/types/basic/observable.d.ts +34 -0
  82. package/dist/types/basic/observable.d.ts.map +1 -0
  83. package/dist/types/basic/signal.d.ts +37 -0
  84. package/dist/types/basic/signal.d.ts.map +1 -0
  85. package/dist/types/basic/state.d.ts +26 -0
  86. package/dist/types/basic/state.d.ts.map +1 -0
  87. package/dist/types/creators.d.ts +29 -13
  88. package/dist/types/creators.d.ts.map +1 -1
  89. package/dist/types/index.d.ts +3 -9
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/advanced/index.ts +10 -0
  93. package/src/{map.ts → advanced/map.ts} +14 -14
  94. package/src/advanced/resource.ts +56 -0
  95. package/src/{resource.ts → advanced/resourceAsync.ts} +9 -16
  96. package/src/advanced/stream.ts +87 -0
  97. package/src/advanced/streamAsync.ts +82 -0
  98. package/src/basic/constant.ts +64 -0
  99. package/src/basic/derivation.ts +86 -0
  100. package/src/basic/effect.ts +96 -0
  101. package/src/basic/index.ts +8 -0
  102. package/src/basic/observable.ts +51 -0
  103. package/src/basic/signal.ts +105 -0
  104. package/src/basic/state.ts +39 -0
  105. package/src/creators.ts +54 -15
  106. package/src/index.ts +21 -11
  107. package/test/constant.test.ts +46 -0
  108. package/test/derivation.test.ts +30 -6
  109. package/test/effect.test.ts +29 -0
  110. package/test/map.test.ts +38 -0
  111. package/test/resource.test.ts +18 -16
  112. package/test/resourceAsync.test.ts +108 -0
  113. package/test/signal.test.ts +18 -1
  114. package/test/state.test.ts +107 -2
  115. package/test/stream.test.ts +38 -13
  116. package/test/streamAsync.test.ts +194 -0
  117. package/tsconfig.json +3 -1
  118. package/api/doc/picoflow.flowdisposer.md +0 -13
  119. package/api/doc/picoflow.flowsetter.md +0 -13
  120. package/api/doc/picoflow.flowstate._constructor_.md +0 -49
  121. package/api/doc/picoflow.flowstate.get.md +0 -23
  122. package/api/doc/picoflow.flowupdater.md +0 -19
  123. package/api/picoflow.api.md +0 -145
  124. package/dist/types/derivation.d.ts +0 -58
  125. package/dist/types/derivation.d.ts.map +0 -1
  126. package/dist/types/effect.d.ts +0 -108
  127. package/dist/types/effect.d.ts.map +0 -1
  128. package/dist/types/map.d.ts.map +0 -1
  129. package/dist/types/observable.d.ts +0 -40
  130. package/dist/types/observable.d.ts.map +0 -1
  131. package/dist/types/resource.d.ts.map +0 -1
  132. package/dist/types/signal.d.ts +0 -111
  133. package/dist/types/signal.d.ts.map +0 -1
  134. package/dist/types/state.d.ts +0 -39
  135. package/dist/types/state.d.ts.map +0 -1
  136. package/dist/types/stream.d.ts +0 -71
  137. package/dist/types/stream.d.ts.map +0 -1
  138. package/src/derivation.ts +0 -96
  139. package/src/effect.ts +0 -152
  140. package/src/observable.ts +0 -50
  141. package/src/signal.ts +0 -166
  142. package/src/state.ts +0 -52
  143. package/src/stream.ts +0 -99
@@ -0,0 +1,51 @@
1
+ import { FlowEffect } from "./effect";
2
+ import { FlowSignal } from "./signal";
3
+
4
+ /**
5
+ * A function that retrieves the current value from a FlowObservable.
6
+ * @typeparam T - The type of the value held by the observable.
7
+ * @param observable - The FlowObservable instance to retrieve the value from.
8
+ * @returns The current value of the observable.
9
+ * @public
10
+ */
11
+ export type FlowGetter = <T>(observable: FlowObservable<T>) => T;
12
+
13
+ /**
14
+ * Represents a reactive observable that holds and tracks a value.
15
+ *
16
+ *
17
+ * @remarks Subclasses must implement the {@link FlowObservable.get} method to return the current value.
18
+ * @typeparam T - The type of the value held by the observable.
19
+ * @public
20
+ */
21
+ export abstract class FlowObservable<T> extends FlowSignal {
22
+ /**
23
+ * Retrieves the current value stored in the observable.
24
+ * Subclasses must override this method to provide the current value.
25
+ * @returns The current value of type T.
26
+ * @public
27
+ */
28
+ abstract get(): T;
29
+
30
+ /* INTERNAL -------------------------------------------*/
31
+
32
+ /*@internal*/ protected _value!: T;
33
+
34
+ /*@internal*/ _getFrom(listener: FlowObservable<unknown> | FlowEffect): T {
35
+ listener._registerDependency(this);
36
+ return this.get();
37
+ }
38
+
39
+ /**
40
+ * Subscribes a listener function to changes of the observable.
41
+ * The listener is executed immediately with the current value and on subsequent updates.
42
+ * @param listener - A callback function that receives the new value.
43
+ * @returns A disposer function to cancel the subscription.
44
+ */
45
+ subscribe(listener: (value: T) => void): () => void {
46
+ const effect = new FlowEffect((get) => {
47
+ listener(get(this));
48
+ });
49
+ return () => effect.dispose();
50
+ }
51
+ }
@@ -0,0 +1,105 @@
1
+ import type { FlowEffect } from "./effect";
2
+
3
+ /**
4
+ * A function for watching a FlowSignal.
5
+ * @param signal - The FlowSignal that is being observed.
6
+ * @public
7
+ */
8
+ export type FlowWatcher = (signal: FlowSignal) => void;
9
+
10
+ /**
11
+ * Represents a reactive signal.
12
+ *
13
+ * @remarks Use FlowSignal to create reactive streams that notify listeners and execute associated effects.
14
+ * Signals can be triggered and disposed. Once disposed, interactions with the signal will throw errors.
15
+ * @public
16
+ */
17
+ export class FlowSignal {
18
+ /**
19
+ * Triggers the FlowSignal.
20
+ * Notifies all registered listeners and schedules execution of associated effects.
21
+ * @throws If the FlowSignal has already been disposed.
22
+ * @public
23
+ */
24
+ public trigger(): void {
25
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
26
+ this._notify();
27
+ }
28
+
29
+ /**
30
+ * Disposes the FlowSignal.
31
+ * Cleans up all registered effects, listeners, and dependencies.
32
+ * Once disposed, further usage of the signal will throw an error.
33
+ * @throws If the FlowSignal is already disposed.
34
+ * @public
35
+ */
36
+ public dispose(): void {
37
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
38
+ Array.from(this._effects).forEach((effect) => effect.dispose());
39
+ Array.from(this._listeners).forEach((listener) => listener.dispose());
40
+ Array.from(this._dependencies).forEach((dependency) => {
41
+ this._unregisterDependency(dependency);
42
+ });
43
+ this._disposed = true;
44
+ }
45
+
46
+ /**
47
+ * Indicates whether the FlowSignal has been disposed.
48
+ * @remarks Once disposed, the signal should not be used.
49
+ * @public
50
+ */
51
+ public get disposed(): boolean {
52
+ return this._disposed;
53
+ }
54
+
55
+ /* INTERNAL ------------------------------------------------------------- */
56
+
57
+ /*@internal*/ protected _disposed = false;
58
+
59
+ /*@internal*/ protected _dependencies = new Set<FlowSignal>();
60
+
61
+ /*@internal*/ protected _listeners = new Set<FlowSignal>();
62
+
63
+ /*@internal*/ protected _effects = new Set<FlowEffect>();
64
+
65
+ /*@internal*/ _watch(): void {
66
+ /* v8 ignore next 1 */
67
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
68
+ }
69
+
70
+ /*@internal*/ _notify(): void {
71
+ this._listeners.forEach((listener) => listener._notify());
72
+ this._effects.forEach((effect) => effect._exec());
73
+ }
74
+
75
+ /*@internal*/ _watchFrom(listener: FlowSignal | FlowEffect): void {
76
+ listener._registerDependency(this);
77
+ this._watch();
78
+ }
79
+
80
+ /*@internal*/ _registerDependency(dependency: FlowSignal): void {
81
+ this._dependencies.add(dependency);
82
+ dependency._registerListener(this);
83
+ }
84
+
85
+ /*@internal*/ _unregisterDependency(dependency: FlowSignal): void {
86
+ this._dependencies.delete(dependency);
87
+ dependency._unregisterListener(this);
88
+ }
89
+
90
+ /*@internal*/ _registerListener(signal: FlowSignal): void {
91
+ this._listeners.add(signal);
92
+ }
93
+
94
+ /*@internal*/ _unregisterListener(signal: FlowSignal): void {
95
+ this._listeners.delete(signal);
96
+ }
97
+
98
+ /*@internal*/ _registerEffect(effect: FlowEffect): void {
99
+ this._effects.add(effect);
100
+ }
101
+
102
+ /*@internal*/ _unregisterEffect(effect: FlowEffect): void {
103
+ this._effects.delete(effect);
104
+ }
105
+ }
@@ -0,0 +1,39 @@
1
+ import { FlowConstant } from "./constant";
2
+
3
+ /**
4
+ * Represents a reactive state that holds a mutable value.
5
+ *
6
+ * @typeparam T - The type of the state value.
7
+ *
8
+ * @remarks
9
+ * FlowState extends FlowConstant, which provides the {@link FlowConstant.get} method to read
10
+ * the current state. Use the {@link FlowState.set} method to update the state. When the state is updated,
11
+ * subscribers are notified automatically. This class notifies subscribers only when the value changes.
12
+ *
13
+ * @public
14
+ */
15
+ export class FlowState<T> extends FlowConstant<T> {
16
+ /**
17
+ * Updates the state with a new value.
18
+ * @param value - A new value or a callback function that computes a new value based on the current state.
19
+ * @remarks
20
+ * If the computed new value is strictly equal to the current state value, no change is made and subscribers
21
+ * will not be notified. Otherwise, the state is updated and all subscribers are informed of the change.
22
+ * @throws Error if the state has been disposed.
23
+ * @public
24
+ */
25
+ set(value: T | ((current: T) => T)): void {
26
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
27
+
28
+ // compute new value
29
+ const next =
30
+ typeof value === "function"
31
+ ? (value as (current: T) => T)(this._value)
32
+ : value;
33
+
34
+ // apply new value
35
+ if (next === this._value) return;
36
+ this._value = next;
37
+ this._notify();
38
+ }
39
+ }
package/src/creators.ts CHANGED
@@ -1,11 +1,18 @@
1
- import { FlowDerivation } from "./derivation";
2
- import { FlowEffect } from "./effect";
3
- import { FlowMap } from "./map";
4
- import type { FlowGetter } from "./observable";
5
- import { FlowResource } from "./resource";
6
- import { FlowSignal, type FlowWatcher } from "./signal";
7
- import { FlowState } from "./state";
8
- import { FlowStream } from "./stream";
1
+ import {
2
+ FlowMap,
3
+ FlowResource,
4
+ FlowResourceAsync,
5
+ FlowStream,
6
+ FlowStreamAsync,
7
+ } from "./advanced/";
8
+ import {
9
+ FlowConstant,
10
+ FlowDerivation,
11
+ FlowEffect,
12
+ FlowSignal,
13
+ FlowState,
14
+ } from "./basic/";
15
+ import type { FlowGetter, FlowWatcher } from "./basic/";
9
16
 
10
17
  /**
11
18
  * Creates a new reactive signal.
@@ -16,6 +23,16 @@ export function signal(): FlowSignal {
16
23
  return new FlowSignal();
17
24
  }
18
25
 
26
+ /**
27
+ * Creates a new reactive constant.
28
+ * @param value - The value or a function that returns the value.
29
+ * @returns A new instance of {@link FlowConstant}.
30
+ * @public
31
+ */
32
+ export function constant<T>(value: T | (() => T)): FlowConstant<T> {
33
+ return new FlowConstant<T>(value);
34
+ }
35
+
19
36
  /**
20
37
  * Creates a new reactive state holding a value.
21
38
  * @typeparam T - The type of the state value.
@@ -23,7 +40,7 @@ export function signal(): FlowSignal {
23
40
  * @returns A new instance of {@link FlowState}.
24
41
  * @public
25
42
  */
26
- export function state<T>(value: T): FlowState<T> {
43
+ export function state<T>(value: T | (() => T)): FlowState<T> {
27
44
  return new FlowState(value);
28
45
  }
29
46
 
@@ -31,12 +48,22 @@ export function state<T>(value: T): FlowState<T> {
31
48
  * Creates a new reactive resource that asynchronously fetches its value.
32
49
  * @typeparam T - The type of the resource value.
33
50
  * @param fn - An asynchronous function that fetches the resource value.
34
- * @param initial - The initial value of the resource.
35
51
  * @returns A new instance of {@link FlowResource}.
36
52
  * @public
37
53
  */
38
- export function resource<T>(fn: () => Promise<T>, initial: T): FlowResource<T> {
39
- return new FlowResource(fn, initial);
54
+ export function resource<T>(fn: () => Promise<T>): FlowResource<T> {
55
+ return new FlowResource(fn);
56
+ }
57
+
58
+ /**
59
+ * Creates a new reactive asynchronous resource that fetches its value.
60
+ * @typeparam T - The type of the resource value.
61
+ * @param fn - An asynchronous function that fetches the resource value.
62
+ * @returns A new instance of {@link FlowResourceAsync}.
63
+ * @public
64
+ */
65
+ export function resourceAsync<T>(fn: () => Promise<T>): FlowResourceAsync<T> {
66
+ return new FlowResourceAsync(fn);
40
67
  }
41
68
 
42
69
  /**
@@ -44,15 +71,27 @@ export function resource<T>(fn: () => Promise<T>, initial: T): FlowResource<T> {
44
71
  * @typeparam T - The type of the stream value.
45
72
  * @param updater - A function that receives a setter to update the stream's value.
46
73
  * It should return a disposer function to clean up resources.
47
- * @param initial - The initial value of the stream.
48
74
  * @returns A new instance of {@link FlowStream}.
49
75
  * @public
50
76
  */
51
77
  export function stream<T>(
52
78
  updater: (set: (value: T) => void) => () => void,
53
- initial: T,
54
79
  ): FlowStream<T> {
55
- return new FlowStream(updater, initial);
80
+ return new FlowStream(updater);
81
+ }
82
+
83
+ /**
84
+ * Creates a new reactive asynchronous stream.
85
+ * @typeparam T - The type of the stream value.
86
+ * @param updater - A function that receives a setter to update the stream's value.
87
+ * It should return a disposer function to clean up resources.
88
+ * @returns A new instance of {@link FlowStreamAsync}.
89
+ * @public
90
+ */
91
+ export function streamAsync<T>(
92
+ updater: (set: (value: T) => void) => () => void,
93
+ ): FlowStreamAsync<T> {
94
+ return new FlowStreamAsync(updater);
56
95
  }
57
96
 
58
97
  /**
package/src/index.ts CHANGED
@@ -9,22 +9,32 @@
9
9
  export {
10
10
  signal,
11
11
  state,
12
+ constant,
12
13
  resource,
13
14
  stream,
14
15
  derivation,
15
16
  effect,
16
17
  map,
18
+ streamAsync,
19
+ resourceAsync,
17
20
  } from "./creators";
18
- export type { FlowDerivation } from "./derivation";
19
- export type { FlowEffect } from "./effect";
20
- export type { FlowGetter, FlowObservable } from "./observable";
21
- export type { FlowResource } from "./resource";
22
- export type { FlowSignal, FlowWatcher } from "./signal";
23
- export type { FlowState } from "./state";
24
- export type { FlowMap } from "./map";
25
21
  export type {
22
+ FlowDerivation,
23
+ FlowEffect,
24
+ FlowGetter,
25
+ FlowObservable,
26
+ FlowSignal,
27
+ FlowWatcher,
28
+ FlowState,
29
+ FlowConstant,
30
+ } from "./basic/";
31
+ export type {
32
+ FlowResource,
33
+ FlowMap,
34
+ FlowResourceAsync,
35
+ FlowStreamAsync,
26
36
  FlowStream,
27
- FlowDisposer,
28
- FlowSetter,
29
- FlowUpdater,
30
- } from "./stream";
37
+ FlowStreamDisposer,
38
+ FlowStreamSetter,
39
+ FlowStreamUpdater,
40
+ } from "./advanced/";
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { constant, effect } from "#package";
3
+
4
+ describe("constant", () => {
5
+ test("is initialized", () => {
6
+ const $constant = constant(1);
7
+ expect($constant.get()).toBe(1);
8
+ });
9
+
10
+ test("is lazy initialized", () => {
11
+ const $constant = constant(() => 1);
12
+ expect($constant.get()).toBe(1);
13
+ });
14
+
15
+ test("get throws when disposed", () => {
16
+ const $constant = constant(1);
17
+
18
+ expect($constant.get()).toBe(1);
19
+
20
+ $constant.dispose();
21
+
22
+ expect(() => $constant.get()).toThrow(
23
+ "[PicoFlow] Primitive is disposed",
24
+ );
25
+ });
26
+ });
27
+
28
+ describe("effect", () => {
29
+ test("called with init value", () => {
30
+ const $constant = constant(1);
31
+ const effectFn = vi.fn();
32
+ effect((get) => effectFn(get($constant)));
33
+
34
+ expect(effectFn).toHaveBeenCalledTimes(1);
35
+ expect(effectFn).toHaveBeenLastCalledWith(1);
36
+ });
37
+
38
+ test("called with lazy init value", () => {
39
+ const $constant = constant(() => 1);
40
+ const effectFn = vi.fn();
41
+ effect((get) => effectFn(get($constant)));
42
+
43
+ expect(effectFn).toHaveBeenCalledTimes(1);
44
+ expect(effectFn).toHaveBeenLastCalledWith(1);
45
+ });
46
+ });
@@ -21,7 +21,9 @@ describe("derivation", () => {
21
21
  expect($derivation.get()).toBe(4);
22
22
 
23
23
  $derivation.dispose();
24
- expect(() => $derivation.get()).toThrow("Effect is disposed");
24
+ expect(() => $derivation.get()).toThrow(
25
+ "[PicoFlow] Primitive is disposed",
26
+ );
25
27
  });
26
28
 
27
29
  test("get throws when state is disposed", () => {
@@ -33,7 +35,9 @@ describe("derivation", () => {
33
35
  expect($derivation.get()).toBe(4);
34
36
 
35
37
  $state.dispose();
36
- expect(() => $derivation.get()).toThrow("Effect is disposed");
38
+ expect(() => $derivation.get()).toThrow(
39
+ "[PicoFlow] Primitive is disposed",
40
+ );
37
41
  });
38
42
 
39
43
  test("is updated (chained dependencies)", () => {
@@ -111,8 +115,8 @@ describe("derivation", () => {
111
115
  resourceCounter++;
112
116
  return resourceCounter;
113
117
  };
114
- const $resource = resource(fetchResource, 0);
115
- const $derivation = derivation((get) => get($resource) * 2);
118
+ const $resource = resource(fetchResource);
119
+ const $derivation = derivation((get) => (get($resource) ?? 0) * 2);
116
120
 
117
121
  expect($derivation.get()).toBe(0);
118
122
 
@@ -374,6 +378,26 @@ describe("effect", () => {
374
378
  expect(effectFn).toHaveBeenLastCalledWith(4);
375
379
  });
376
380
 
381
+ test("NOT called when derivation is disposed (watch)", () => {
382
+ const $state = state(1);
383
+ const $derivation = derivation((get) => get($state) * 2);
384
+ const effectFn = vi.fn();
385
+ effect((_, watch) => {
386
+ watch($derivation);
387
+ effectFn();
388
+ });
389
+
390
+ expect(effectFn).toHaveBeenCalledTimes(1);
391
+
392
+ $state.set(2);
393
+ expect(effectFn).toHaveBeenCalledTimes(2);
394
+
395
+ $derivation.dispose();
396
+
397
+ $state.set(3);
398
+ expect(effectFn).toHaveBeenCalledTimes(2);
399
+ });
400
+
377
401
  test("NOT called when disposed", () => {
378
402
  const $state = state(1);
379
403
  const $derivation = derivation((get) => get($state) * 2);
@@ -402,8 +426,8 @@ describe("effect", () => {
402
426
  resourceCounter++;
403
427
  return resourceCounter;
404
428
  };
405
- const $resource = resource(fetchResource, 0);
406
- const $derivation = derivation((get) => get($resource) * 2);
429
+ const $resource = resource(fetchResource);
430
+ const $derivation = derivation((get) => (get($resource) ?? 0) * 2);
407
431
  const effectFn = vi.fn();
408
432
  effect((get) => effectFn(get($derivation)));
409
433
 
@@ -0,0 +1,29 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { effect, signal } from "#package";
3
+
4
+ describe("effect", () => {
5
+ test("disposed is correct", () => {
6
+ const $signal = signal();
7
+ const effectFn = vi.fn();
8
+ const $effect = effect((_, watch) => {
9
+ watch($signal);
10
+ effectFn();
11
+ });
12
+
13
+ expect($effect.disposed).toBe(false);
14
+ $effect.dispose();
15
+ expect($effect.disposed).toBe(true);
16
+ });
17
+
18
+ test("throw when disposed twice", () => {
19
+ const $signal = signal();
20
+ const effectFn = vi.fn();
21
+ const $effect = effect((_, watch) => {
22
+ watch($signal);
23
+ effectFn();
24
+ });
25
+
26
+ $effect.dispose();
27
+ expect(() => $effect.dispose()).toThrow("Effect is disposed");
28
+ });
29
+ });
package/test/map.test.ts CHANGED
@@ -2,6 +2,20 @@ import { describe, expect, test, vi } from "vitest";
2
2
  import { effect, map } from "#package";
3
3
 
4
4
  describe("map", () => {
5
+ test("is initialized", () => {
6
+ const $map = map({
7
+ key1: 1,
8
+ key2: 2,
9
+ key3: 3,
10
+ });
11
+
12
+ expect(Array.from($map.get().entries())).toEqual([
13
+ ["key1", 1],
14
+ ["key2", 2],
15
+ ["key3", 3],
16
+ ]);
17
+ });
18
+
5
19
  test("is updated", () => {
6
20
  const $map = map();
7
21
 
@@ -26,6 +40,30 @@ describe("map", () => {
26
40
  expect($map.get().get("key2")).toBe(2);
27
41
  expect($map.$lastDeleted.get()).toEqual({ key: "key1", value: 3 });
28
42
  });
43
+
44
+ test("get throws when disposed", () => {
45
+ const $map = map({ key1: 1, key2: 2, key3: 3 });
46
+ $map.dispose();
47
+ expect(() => $map.get()).toThrowError(
48
+ "[PicoFlow] Primitive is disposed",
49
+ );
50
+ });
51
+
52
+ test("set throws when disposed", () => {
53
+ const $map = map<string, number>();
54
+ $map.dispose();
55
+ expect(() => $map.setAt("key1", 2)).toThrowError(
56
+ "[PicoFlow] Primitive is disposed",
57
+ );
58
+ });
59
+
60
+ test("delete throws when disposed", () => {
61
+ const $map = map<string, number>({ key1: 1, key2: 2, key3: 3 });
62
+ $map.dispose();
63
+ expect(() => $map.delete("key1")).toThrowError(
64
+ "[PicoFlow] Primitive is disposed",
65
+ );
66
+ });
29
67
  });
30
68
 
31
69
  describe("effect", () => {
@@ -9,8 +9,8 @@ describe("resource", () => {
9
9
  return resourceCounter;
10
10
  };
11
11
 
12
- const $resource = resource(fetchResource, 0);
13
- expect($resource.get()).toBe(0);
12
+ const $resource = resource(fetchResource);
13
+ expect($resource.get()).toBe(undefined);
14
14
 
15
15
  await $resource.fetch();
16
16
  expect($resource.get()).toBe(1);
@@ -26,15 +26,15 @@ describe("resource", () => {
26
26
  return resourceCounter;
27
27
  };
28
28
 
29
- const $resource = resource(fetchResource, 0);
30
- expect($resource.get()).toBe(0);
29
+ const $resource = resource(fetchResource);
30
+ expect($resource.get()).toBe(undefined);
31
31
 
32
32
  await $resource.fetch();
33
33
  expect($resource.get()).toBe(1);
34
34
 
35
35
  $resource.dispose();
36
36
  await expect(() => $resource.fetch()).rejects.toThrow(
37
- "Resource is disposed",
37
+ "[PicoFlow] Primitive is disposed",
38
38
  );
39
39
  });
40
40
 
@@ -45,14 +45,16 @@ describe("resource", () => {
45
45
  return resourceCounter;
46
46
  };
47
47
 
48
- const $resource = resource(fetchResource, 0);
49
- expect($resource.get()).toBe(0);
48
+ const $resource = resource(fetchResource);
49
+ expect($resource.get()).toBe(undefined);
50
50
 
51
51
  await $resource.fetch();
52
52
  expect($resource.get()).toBe(1);
53
53
 
54
54
  $resource.dispose();
55
- expect(() => $resource.get()).toThrow("Resource is disposed");
55
+ expect(() => $resource.get()).toThrow(
56
+ "[PicoFlow] Primitive is disposed",
57
+ );
56
58
  });
57
59
  });
58
60
 
@@ -64,12 +66,12 @@ describe("effect", () => {
64
66
  return resourceCounter;
65
67
  };
66
68
 
67
- const $resource = resource(fetchResource, 0);
69
+ const $resource = resource(fetchResource);
68
70
  const effectFn = vi.fn();
69
71
  effect((get) => effectFn(get($resource)));
70
72
 
71
73
  expect(effectFn).toHaveBeenCalledTimes(1);
72
- expect(effectFn).toHaveBeenLastCalledWith(0);
74
+ expect(effectFn).toHaveBeenLastCalledWith(undefined);
73
75
 
74
76
  await $resource.fetch();
75
77
  expect(effectFn).toHaveBeenCalledTimes(2);
@@ -85,19 +87,19 @@ describe("effect", () => {
85
87
  return 0;
86
88
  };
87
89
 
88
- const $resource = resource(fetchResource, 0);
90
+ const $resource = resource(fetchResource);
89
91
  const effectFn = vi.fn();
90
92
  effect((get) => effectFn(get($resource)));
91
93
 
92
94
  expect(effectFn).toHaveBeenCalledTimes(1);
93
- expect(effectFn).toHaveBeenLastCalledWith(0);
95
+ expect(effectFn).toHaveBeenLastCalledWith(undefined);
94
96
 
95
97
  await $resource.fetch();
96
- expect(effectFn).toHaveBeenCalledTimes(1);
98
+ expect(effectFn).toHaveBeenCalledTimes(2);
97
99
  expect(effectFn).toHaveBeenLastCalledWith(0);
98
100
 
99
101
  await $resource.fetch();
100
- expect(effectFn).toHaveBeenCalledTimes(1);
102
+ expect(effectFn).toHaveBeenCalledTimes(2);
101
103
  expect(effectFn).toHaveBeenLastCalledWith(0);
102
104
  });
103
105
 
@@ -108,12 +110,12 @@ describe("effect", () => {
108
110
  return resourceCounter;
109
111
  };
110
112
 
111
- const $resource = resource(fetchResource, 0);
113
+ const $resource = resource(fetchResource);
112
114
  const effectFn = vi.fn();
113
115
  const $effect = effect((get) => effectFn(get($resource)));
114
116
 
115
117
  expect(effectFn).toHaveBeenCalledTimes(1);
116
- expect(effectFn).toHaveBeenLastCalledWith(0);
118
+ expect(effectFn).toHaveBeenLastCalledWith(undefined);
117
119
 
118
120
  await $resource.fetch();
119
121
  expect(effectFn).toHaveBeenCalledTimes(2);