@virentia/effector 0.2.1 → 0.3.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/README.md CHANGED
@@ -10,53 +10,141 @@ Use this package when Virentia models need to call Effector units, or existing E
10
10
  pnpm add @virentia/effector effector @virentia/core
11
11
  ```
12
12
 
13
- ## Basic usage
13
+ ## Associate scopes
14
14
 
15
15
  ```ts
16
- import { scope, scoped } from "@virentia/core";
17
- import { createEffectorCompatibility } from "@virentia/effector";
18
- import { allSettled, fork } from "effector";
16
+ import { scope } from "@virentia/core";
17
+ import { associate } from "@virentia/effector";
18
+ import { fork } from "effector";
19
19
 
20
- const effector = createEffectorCompatibility();
21
20
  const virentiaScope = scope();
22
21
  const effectorScope = fork();
23
22
 
24
- const association = effector.associate({
23
+ associate({
25
24
  virentia: virentiaScope,
26
25
  effector: effectorScope,
27
26
  });
28
27
  ```
29
28
 
30
- A Virentia scope and an Effector scope are required. The association is only a disposable link between them.
29
+ A Virentia scope and an Effector scope are associated globally through weak maps.
30
+ The examples below use these associated scopes.
31
31
 
32
- ## Links
32
+ ## Universal Ports
33
+
34
+ Use `fool(unit)` at feature boundaries. The result is a pass-through unit for one direction: one feature writes to the port, another feature reads from it. Effector features can read or write that port as `clock`, `source`, or `target`; Virentia features can read it as `on` or call it from `run`/`scoped`.
35
+
36
+ The unit keeps the natural call style of the system it came from. A port created from `event()` is called like a Virentia event. A port created from `createEvent()` is launched like an Effector event.
37
+
38
+ ## Virentia Feature To Effector Feature
33
39
 
34
40
  ```ts
35
- effector.link(virentiaSubmitted, effectorSubmitted, ({ id }) => id);
36
-
37
- await scoped(virentiaScope, () =>
38
- allSettled(effectorSubmitted, {
39
- scope: effectorScope,
40
- params: "user:1",
41
- }),
42
- );
41
+ import { event, scoped } from "@virentia/core";
42
+ import { fool } from "@virentia/effector";
43
+ import { createEvent, createStore, sample } from "effector";
44
+
45
+ const checkoutRequested = fool(event<{ orderId: string }>());
46
+
47
+ function createVirentiaCheckoutFeature() {
48
+ return {
49
+ requestCheckout: checkoutRequested,
50
+ };
51
+ }
52
+
53
+ function createEffectorBillingFeature() {
54
+ const $session = createStore({ token: "session-token" });
55
+ const billingStarted = createEvent<{ orderId: string; token: string }>();
56
+ const $startedOrders = createStore<string[]>([]).on(billingStarted, (orders, order) => [
57
+ ...orders,
58
+ order.orderId,
59
+ ]);
60
+
61
+ sample({
62
+ clock: checkoutRequested,
63
+ source: $session,
64
+ fn: (session, request) => ({
65
+ orderId: request.orderId,
66
+ token: session.token,
67
+ }),
68
+ target: billingStarted,
69
+ });
70
+
71
+ return {
72
+ $startedOrders,
73
+ billingStarted,
74
+ };
75
+ }
76
+
77
+ const billing = createEffectorBillingFeature();
78
+ const checkout = createVirentiaCheckoutFeature();
79
+
80
+ await scoped(virentiaScope, async () => {
81
+ await checkout.requestCheckout({ orderId: "order:1" });
82
+ });
43
83
  ```
44
84
 
45
- The association is only a lifetime handle. Use `scoped`, Effector `allSettled`, `scopeBind`, or UI Providers to choose scopes.
85
+ The Virentia feature owns the command and calls it naturally. The Effector feature consumes that one pass-through port as its `clock`, then keeps its own output and state in `billing`.
46
86
 
47
- ## Effector sample
87
+ ## Effector Feature To Virentia Feature
48
88
 
49
89
  ```ts
50
- import { sample } from "effector";
51
-
52
- sample({
53
- clock: effectorUserClicked,
54
- source: $session,
55
- fn: (session, userId) => ({ userId, token: session.token }),
56
- target: effector.asEffector(virentiaUserOpened),
90
+ import { event, reaction, store } from "@virentia/core";
91
+ import { fool } from "@virentia/effector";
92
+ import { allSettled, createEvent, sample } from "effector";
93
+
94
+ const routeOpened = fool(createEvent<string>());
95
+
96
+ function createEffectorRoutesFeature() {
97
+ const profileClicked = createEvent<string>();
98
+
99
+ sample({
100
+ clock: profileClicked,
101
+ target: routeOpened,
102
+ });
103
+
104
+ return {
105
+ profileClicked,
106
+ };
107
+ }
108
+
109
+ function createVirentiaProfileFeature() {
110
+ const profileLoaded = event<{ userId: string; name: string }>();
111
+ const loadedCount = store(0);
112
+
113
+ reaction({
114
+ on: routeOpened,
115
+ run(userId) {
116
+ profileLoaded({
117
+ userId,
118
+ name: "Ada",
119
+ });
120
+ },
121
+ });
122
+
123
+ reaction({
124
+ on: profileLoaded,
125
+ run() {
126
+ loadedCount.value += 1;
127
+ },
128
+ });
129
+
130
+ return {
131
+ loadedCount,
132
+ };
133
+ }
134
+
135
+ const routes = createEffectorRoutesFeature();
136
+ createVirentiaProfileFeature();
137
+
138
+ await allSettled(routes.profileClicked, {
139
+ scope: effectorScope,
140
+ params: "user:1",
57
141
  });
58
142
  ```
59
143
 
144
+ The Effector feature owns navigation and launches its own event naturally. The Virentia feature listens to that universal port with `on`, then calls its own Virentia port from `run`.
145
+
146
+ Use `scoped`, Effector `allSettled`, `scopeBind`, or UI Providers to choose scopes.
147
+
60
148
  ## Tests
61
149
 
62
150
  ```sh