@virentia/effector 0.1.1 → 0.2.1

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/dist/index.mjs CHANGED
@@ -1,499 +1,311 @@
1
- import * as core from "@virentia/core";
2
- //#region lib/types.ts
3
- const unitKind = Symbol("virentia.effector.unit");
4
- //#endregion
5
- //#region lib/guards.ts
6
- const is = {
7
- unit(value) {
8
- return isUnit(value);
9
- },
10
- event(value) {
11
- return isEvent(value);
12
- },
13
- store(value) {
14
- return isStore(value);
15
- },
16
- effect(value) {
17
- return isEffect(value);
18
- },
19
- targetable(value) {
20
- return isTargetable(value);
21
- }
22
- };
23
- function isUnit(value) {
24
- return Boolean(value && (typeof value === "object" || typeof value === "function") && unitKind in value);
25
- }
26
- function isEvent(value) {
27
- return isUnit(value) && value[unitKind] === "event";
28
- }
29
- function isStore(value) {
30
- return isUnit(value) && value[unitKind] === "store";
31
- }
32
- function isEffect(value) {
33
- return isUnit(value) && value[unitKind] === "effect";
34
- }
35
- function isTargetable(value) {
36
- return isEvent(value) || isEffect(value) || isStore(value) && typeof value.setState === "function";
37
- }
38
- function isScope(value) {
39
- return Boolean(value && typeof value === "object" && "__core" in value && !(unitKind in value));
40
- }
41
- function isScopeError(error) {
42
- return error instanceof Error && error.message === "Scope is required";
43
- }
44
- //#endregion
45
- //#region lib/shared.ts
46
- const defaultScope = core.scope();
47
- const compatScopes = /* @__PURE__ */ new WeakMap();
48
- const storesBySid = /* @__PURE__ */ new Map();
49
- const nativeStoreKeys = new Set([
50
- "node",
51
- "writable",
52
- "subscribe",
53
- "map",
54
- "filter",
55
- "filterMap"
56
- ]);
57
- function createCompatScope(scope) {
58
- const existing = compatScopes.get(scope);
59
- if (existing) return existing;
60
- const compatScope = {
61
- __core: scope,
62
- __changedSids: /* @__PURE__ */ new Set(),
63
- getState(store) {
64
- return store.getState(this);
65
- }
1
+ import * as virentia from "@virentia/core";
2
+ import { allSettled, clearNode, createEffect, createEvent, createNode, createWatch, is, launch, step } from "effector";
3
+ //#region lib/index.ts
4
+ function createEffectorCompatibility(_options = {}) {
5
+ const runtimes = /* @__PURE__ */ new Set();
6
+ const effectorByVirentia = /* @__PURE__ */ new WeakMap();
7
+ const virentiaByEffector = /* @__PURE__ */ new WeakMap();
8
+ const installers = /* @__PURE__ */ new Set();
9
+ return {
10
+ associate,
11
+ ensureAssociation(config = {}) {
12
+ return ensureAssociation(config);
13
+ },
14
+ link(from, to, map) {
15
+ const definition = {
16
+ from,
17
+ to,
18
+ map
19
+ };
20
+ const installer = (runtime) => installLink(runtime, definition);
21
+ return registerInstaller(installer);
22
+ },
23
+ asEffector: ((unit) => createEffectorAdapter(unit)),
24
+ asVirentia: ((unit) => createVirentiaAdapter(unit))
66
25
  };
67
- compatScopes.set(scope, compatScope);
68
- return compatScope;
69
- }
70
- function inScope(scope, fn) {
71
- if (scope) return core.scoped(scope.__core, fn);
72
- try {
73
- return fn();
74
- } catch (error) {
75
- if (!isScopeError(error)) throw error;
76
- return core.scoped(defaultScope, fn);
77
- }
78
- }
79
- function callWithFallback(unit) {
80
- return ((...args) => {
81
- try {
82
- return unit(...args).catch((error) => {
83
- if (!isScopeError(error)) throw error;
84
- return core.scoped(defaultScope, () => unit(...args));
85
- });
86
- } catch (error) {
87
- if (!isScopeError(error)) throw error;
88
- return core.scoped(defaultScope, () => unit(...args));
26
+ function associate(config) {
27
+ if (!config.virentia) throw new Error("Effector compatibility association requires a Virentia scope");
28
+ if (!config.effector) throw new Error("Effector compatibility association requires an Effector scope");
29
+ assertScopesAvailable(config);
30
+ const existing = findRuntime(config);
31
+ if (existing) {
32
+ existing.assertSamePair(config);
33
+ return existing;
89
34
  }
90
- });
91
- }
92
- function readSource(source) {
93
- if (isStore(source)) return source.getState();
94
- if (Array.isArray(source)) return source.map((store) => store.getState());
95
- return Object.fromEntries(Object.entries(source).map(([key, store]) => [key, store.getState()]));
96
- }
97
- function sourceToClock(source) {
98
- if (!source) throw new Error("sample: clock or source is required");
99
- if (isStore(source)) return source;
100
- return Object.values(source);
101
- }
102
- function passesFilter(filter, source, clock) {
103
- if (!filter) return true;
104
- if (isStore(filter)) return filter.getState();
105
- return filter(source, clock);
106
- }
107
- function launchTarget(target, payload) {
108
- for (const unit of toArray(target)) if (isStore(unit)) unit.setState(payload);
109
- else unit(payload);
110
- }
111
- function computeCombined(shape, fn) {
112
- const value = readSource(shape);
113
- return fn ? fn(value) : value;
114
- }
115
- function registerStore(store) {
116
- if (store.sid) storesBySid.set(store.sid, store);
117
- }
118
- function markScopeChanged(scope, sid) {
119
- if (sid) createCompatScope(scope ?? defaultScope).__changedSids.add(sid);
120
- }
121
- function applyStoreValues(scope, values) {
122
- if (Array.isArray(values)) {
123
- for (const [store, value] of values) store.setState(value, scope);
124
- return;
35
+ const runtime = new EffectorRuntimeImpl({
36
+ ...config,
37
+ release: () => {
38
+ runtimes.delete(runtime);
39
+ effectorByVirentia.delete(config.virentia);
40
+ virentiaByEffector.delete(config.effector);
41
+ }
42
+ });
43
+ runtimes.add(runtime);
44
+ effectorByVirentia.set(config.virentia, config.effector);
45
+ virentiaByEffector.set(config.effector, config.virentia);
46
+ for (const installer of installers) runtime.addInstaller(installer);
47
+ return runtime;
125
48
  }
126
- if (values instanceof Map) {
127
- for (const [store, value] of values) applyStoreValue(scope, store, value);
128
- return;
49
+ function assertScopesAvailable(config) {
50
+ const existingEffector = effectorByVirentia.get(config.virentia);
51
+ if (existingEffector && existingEffector !== config.effector) throw new Error("Virentia scope is already associated with another Effector scope");
52
+ const existingVirentia = virentiaByEffector.get(config.effector);
53
+ if (existingVirentia && existingVirentia !== config.virentia) throw new Error("Effector scope is already associated with another Virentia scope");
129
54
  }
130
- for (const [sid, value] of Object.entries(values)) applyStoreValue(scope, sid, value);
131
- }
132
- function getName(input) {
133
- if (typeof input === "string") return input;
134
- if (input && (typeof input === "object" || typeof input === "function") && "shortName" in input) return String(input.shortName ?? "unit");
135
- if (input && (typeof input === "object" || typeof input === "function") && "name" in input) return String(input.name ?? "unit");
136
- return "unit";
137
- }
138
- function toArray(value) {
139
- return Array.isArray(value) ? value : [value];
140
- }
141
- function applyStoreValue(scope, storeOrSid, value) {
142
- if (typeof storeOrSid === "string") {
143
- storesBySid.get(storeOrSid)?.setState(value, scope);
144
- return;
55
+ function ensureAssociation(config = {}) {
56
+ const runtime = findRuntime(config);
57
+ if (!runtime) throw createMissingRuntimeError(config);
58
+ return runtime;
145
59
  }
146
- storeOrSid.setState(value, scope);
147
- }
148
- //#endregion
149
- //#region lib/store.ts
150
- function createStore(defaultState, config) {
151
- const box = core.store({ value: defaultState });
152
- const updates = createEvent({ name: `${config?.name ?? "store"} updates` });
153
- const store = createStoreFromBox(box, updates, defaultState, config?.name, config?.sid);
154
- registerStore(store);
155
- box.subscribe((next, scope) => {
156
- markScopeChanged(scope, store.sid);
157
- core.run({
158
- unit: updates.__core.node,
159
- payload: next.value,
160
- scope
161
- });
162
- });
163
- return store;
164
- }
165
- function createStoreFromBox(box, updates, defaultState, name = "store", sid) {
166
- const result = {
167
- [unitKind]: "store",
168
- __box: box,
169
- __core: updates,
170
- node: updates.__core.node,
171
- shortName: name,
172
- sid,
173
- defaultState,
174
- updates,
175
- getType: () => name,
176
- getState(scope) {
177
- return readBox(box, scope);
178
- },
179
- setState(value, scope) {
180
- writeBox(box, value, scope);
181
- },
182
- watch(fn) {
183
- fn(readBox(box));
184
- return box.subscribe((next) => {
185
- fn(next.value);
186
- });
187
- },
188
- map(fn) {
189
- const mapped = createStore(fn(result.getState()));
190
- core.scoped(defaultScope, () => {
191
- core.reaction(() => {
192
- mapped.setState(fn(result.getState()));
193
- });
194
- });
195
- return mapped;
196
- },
197
- on(trigger, reducer) {
198
- core.reaction({
199
- on: trigger,
200
- run: (payload) => {
201
- result.setState(reducer(result.getState(), payload));
202
- }
203
- });
204
- return result;
205
- },
206
- reset(trigger) {
207
- for (const unit of toArray(trigger)) core.reaction({
208
- on: unit,
209
- run: () => {
210
- result.setState(defaultState);
211
- }
212
- });
213
- return result;
60
+ function findRuntime(config = {}) {
61
+ if (config.virentia) {
62
+ const effectorScope = effectorByVirentia.get(config.virentia);
63
+ const runtime = effectorScope ? findRuntimeByPair(config.virentia, effectorScope) : null;
64
+ if (runtime) return runtime;
214
65
  }
215
- };
216
- return result;
217
- }
218
- function wrapNativeStore(store, defaultState, name) {
219
- const updates = createEvent(`${name}.updates`);
220
- const result = {
221
- [unitKind]: "store",
222
- __core: updates,
223
- node: updates.__core.node,
224
- shortName: name,
225
- defaultState,
226
- updates,
227
- getType: () => name,
228
- getState(scope) {
229
- return readNativeStore(store, scope);
230
- },
231
- watch(fn) {
232
- fn(readNativeStore(store));
233
- return store.subscribe((next) => {
234
- fn(next);
66
+ if (config.effector) {
67
+ const virentiaScope = virentiaByEffector.get(config.effector);
68
+ const runtime = virentiaScope ? findRuntimeByPair(virentiaScope, config.effector) : null;
69
+ if (runtime) return runtime;
70
+ }
71
+ return null;
72
+ }
73
+ function findRuntimeByPair(virentiaScope, effectorScope) {
74
+ for (const runtime of runtimes) if (runtime.virentia === virentiaScope && runtime.effector === effectorScope) return runtime;
75
+ return null;
76
+ }
77
+ function registerInstaller(installer) {
78
+ installers.add(installer);
79
+ for (const runtime of runtimes) runtime.addInstaller(installer);
80
+ return () => {
81
+ installers.delete(installer);
82
+ for (const runtime of runtimes) runtime.removeInstaller(installer);
83
+ };
84
+ }
85
+ function createEffectorAdapter(unit) {
86
+ if (isEffectorUnit(unit)) return unit;
87
+ if (isVirentiaEffect(unit)) {
88
+ const scopeQueue = [];
89
+ const adapter = createEffect((payload) => {
90
+ return resolveRuntimeFromEffectorScope(scopeQueue.shift()).call(unit, payload);
235
91
  });
236
- },
237
- map(fn) {
238
- const mapped = createStore(fn(result.getState()));
239
- core.scoped(defaultScope, () => {
240
- core.reaction(() => {
241
- mapped.setState(fn(result.getState()));
242
- });
92
+ createEffectorScopeNode(adapter, (_payload, scope) => {
93
+ scopeQueue.push(scope);
243
94
  });
244
- return mapped;
245
- },
246
- on() {
247
- throw new Error("Store is read-only");
248
- },
249
- reset() {
250
- throw new Error("Store is read-only");
95
+ return adapter;
251
96
  }
252
- };
253
- store.subscribe((next, scope) => {
254
- core.run({
255
- unit: updates.__core.node,
256
- payload: next,
257
- scope
97
+ const adapter = createEvent();
98
+ createEffectorScopeNode(adapter, (payload, scope) => {
99
+ const runtime = resolveRuntimeFromEffectorScope(scope);
100
+ if (runtime.shouldSkipEffector(adapter)) return;
101
+ runtime.emitVirentia(unit, payload, { suppressReaction: true });
258
102
  });
259
- });
260
- return result;
261
- }
262
- function readBox(box, scope) {
263
- return inScope(scope, () => box.value);
264
- }
265
- function writeBox(box, value, scope) {
266
- inScope(scope, () => {
267
- box.value = value;
268
- });
269
- }
270
- function readNativeStore(store, scope) {
271
- return inScope(scope, () => {
272
- const keys = Reflect.ownKeys(store).filter((key) => !nativeStoreKeys.has(key));
273
- if (keys.length === 1 && keys[0] === "value") return Reflect.get(store, "value");
274
- return Object.fromEntries(keys.map((key) => [key, Reflect.get(store, key)]));
275
- });
276
- }
277
- //#endregion
278
- //#region lib/operators.ts
279
- function sample(config) {
280
- const target = config.target ?? createEvent();
281
- const clocks = toArray(config.clock ?? sourceToClock(config.source));
282
- for (const clock of clocks) core.reaction({
283
- on: clock,
284
- run: (clockPayload) => {
285
- const sourceValue = config.source === void 0 ? void 0 : readSource(config.source);
286
- if (!passesFilter(config.filter, sourceValue, clockPayload)) return;
287
- launchTarget(target, config.fn ? config.source === void 0 ? config.fn(clockPayload, clockPayload) : config.fn(sourceValue, clockPayload) : config.source === void 0 ? clockPayload : sourceValue);
288
- }
289
- });
290
- return Array.isArray(target) ? target[0] : target;
291
- }
292
- function combine(...args) {
293
- const fn = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
294
- const shape = args.length === 1 ? args[0] : args;
295
- const result = createStore(computeCombined(shape, fn));
296
- core.scoped(defaultScope, () => {
297
- core.reaction(() => {
298
- result.setState(computeCombined(shape, fn));
103
+ registerInstaller((runtime) => installLink(runtime, {
104
+ from: unit,
105
+ to: adapter
106
+ }));
107
+ return adapter;
108
+ }
109
+ function createVirentiaAdapter(unit) {
110
+ if (isVirentiaUnit(unit)) return unit;
111
+ if (is.effect(unit)) return virentia.effect((payload) => {
112
+ return resolveRuntimeFromVirentiaScope().call(unit, payload);
299
113
  });
300
- });
301
- return result;
302
- }
303
- function split(sourceOrConfig, maybeCases) {
304
- const source = "source" in sourceOrConfig ? sourceOrConfig.source : sourceOrConfig;
305
- const match = "source" in sourceOrConfig ? sourceOrConfig.match : maybeCases ?? {};
306
- const result = {};
307
- if (typeof match === "function") {
308
- const configuredCases = "source" in sourceOrConfig ? sourceOrConfig.cases ?? {} : {};
309
- for (const key of Object.keys(configuredCases)) result[key] = configuredCases[key] ?? createEvent();
310
- } else for (const key of Object.keys(match)) result[key] = createEvent();
311
- result.__ = createEvent();
312
- core.reaction({
313
- on: source,
314
- run: (payload) => {
315
- launchTarget(result[(typeof match === "function" ? match(payload) : Object.keys(match).find((caseName) => match[caseName](payload))) ?? "__"] ?? result.__, payload);
316
- }
317
- });
318
- return result;
319
- }
320
- function createApi(store, reducers) {
321
- const result = {};
322
- for (const key of Object.keys(reducers)) {
323
- const event = createEvent(String(key));
324
- store.on(event, reducers[key]);
325
- result[key] = event;
114
+ const adapter = virentia.event();
115
+ createEffectorScopeNode(unit, (payload, scope) => {
116
+ const runtime = resolveRuntimeFromEffectorScope(scope);
117
+ if (runtime.shouldSkipEffector(unit)) return;
118
+ runtime.emitVirentia(adapter, payload, { suppressReaction: true });
119
+ });
120
+ registerInstaller((runtime) => installLink(runtime, {
121
+ from: adapter,
122
+ to: unit
123
+ }));
124
+ return adapter;
125
+ }
126
+ function resolveRuntimeFromEffectorScope(scope) {
127
+ if (!scope) throw createMissingRuntimeError({});
128
+ const runtime = ensureAssociation({ effector: scope });
129
+ const activeVirentiaScope = virentia.getCurrentScope();
130
+ if (activeVirentiaScope && activeVirentiaScope !== runtime.virentia) throw new Error("Effector scope is associated with another Virentia scope");
131
+ return runtime;
132
+ }
133
+ function resolveRuntimeFromVirentiaScope() {
134
+ const activeVirentiaScope = virentia.getCurrentScope();
135
+ if (!activeVirentiaScope) throw createMissingRuntimeError({});
136
+ return ensureAssociation({ virentia: activeVirentiaScope });
326
137
  }
327
- return result;
328
- }
329
- function restore(unit, defaultState) {
330
- const store = createStore(defaultState);
331
- const source = isEffect(unit) ? unit.doneData : unit;
332
- store.on(source, (_, payload) => payload);
333
- return store;
334
- }
335
- //#endregion
336
- //#region lib/watch.ts
337
- function watchUnit(unit, fn) {
338
- const subscription = core.reaction({
339
- on: unit,
340
- run: fn
341
- });
342
- return () => subscription.stop();
343
- }
344
- //#endregion
345
- //#region lib/event.ts
346
- function createEvent(nameOrConfig) {
347
- const name = typeof nameOrConfig === "string" ? nameOrConfig : nameOrConfig?.name ?? "unit";
348
- return wrapEvent(core.event(), name);
349
- }
350
- function wrapEvent(event, name = "event") {
351
- const result = callWithFallback(typeof event === "function" ? event : ((...payload) => core.allSettled(event, { payload: payload[0] })));
352
- Object.assign(result, {
353
- [unitKind]: "event",
354
- __core: event,
355
- node: event.node,
356
- shortName: name,
357
- getType: () => name,
358
- watch(fn) {
359
- return watchUnit(result, fn);
360
- },
361
- map(fn) {
362
- return wrapEvent(event.map(fn));
363
- },
364
- filter(config) {
365
- const fn = typeof config === "function" ? config : config.fn;
366
- return wrapEvent(event.filter(fn));
367
- },
368
- filterMap(fn) {
369
- return wrapEvent(event.filterMap(fn));
370
- },
371
- prepend(fn) {
372
- const prepended = createEvent();
373
- sample({
374
- clock: prepended,
375
- fn,
376
- target: result
377
- });
378
- return prepended;
379
- }
380
- });
381
- return result;
382
138
  }
383
- //#endregion
384
- //#region lib/effect.ts
385
- function createEffect(handlerOrConfig) {
386
- let handler = typeof handlerOrConfig === "function" ? handlerOrConfig : handlerOrConfig?.handler;
387
- const effectName = getName(handlerOrConfig);
388
- const fx = core.effect((params) => {
389
- if (!handler) throw new Error("Effect handler is not defined");
390
- return handler(params);
391
- });
392
- const result = callWithFallback(((...params) => fx(...params)));
393
- Object.assign(result, {
394
- [unitKind]: "effect",
395
- __core: fx,
396
- node: fx.node,
397
- shortName: effectName,
398
- getType: () => effectName,
399
- watch(fn) {
400
- return watchUnit(wrapEvent(fx.started), fn);
401
- },
402
- done: wrapEvent(fx.done),
403
- fail: wrapEvent(fx.fail),
404
- finally: wrapEvent(fx.finally),
405
- doneData: wrapEvent(fx.doneData),
406
- failData: wrapEvent(fx.failData),
407
- pending: wrapNativeStore(fx.$pending, false, `${effectName}.pending`),
408
- inFlight: wrapNativeStore(fx.$inFlight, 0, `${effectName}.inFlight`),
409
- prepend(fn) {
410
- const prepended = createEvent();
411
- sample({
412
- clock: prepended,
413
- fn,
414
- target: result
139
+ var EffectorRuntimeImpl = class {
140
+ virentia;
141
+ effector;
142
+ disposed = false;
143
+ cleanups = /* @__PURE__ */ new Set();
144
+ cleanupByInstaller = /* @__PURE__ */ new Map();
145
+ releaseAssociation;
146
+ suppressedEffector = /* @__PURE__ */ new Map();
147
+ suppressedVirentia = /* @__PURE__ */ new Map();
148
+ constructor(config) {
149
+ this.virentia = config.virentia;
150
+ this.effector = config.effector;
151
+ this.releaseAssociation = config.release;
152
+ }
153
+ async call(unit, payload) {
154
+ this.assertAlive();
155
+ if (isEffectorUnit(unit)) return allSettled(unit, {
156
+ params: payload,
157
+ scope: this.effector
158
+ });
159
+ if (isVirentiaEffect(unit)) return virentia.scoped(this.virentia, () => unit(payload));
160
+ await virentia.allSettled(unit, {
161
+ payload,
162
+ scope: this.virentia
163
+ });
164
+ }
165
+ trackCleanup(unsubscribe) {
166
+ this.cleanups.add(unsubscribe);
167
+ return () => {
168
+ this.cleanups.delete(unsubscribe);
169
+ unsubscribe();
170
+ };
171
+ }
172
+ dispose() {
173
+ if (this.disposed) return;
174
+ this.disposed = true;
175
+ for (const cleanup of [...this.cleanups]) cleanup();
176
+ this.cleanups.clear();
177
+ this.cleanupByInstaller.clear();
178
+ this.releaseAssociation();
179
+ }
180
+ addInstaller(installer) {
181
+ if (this.disposed || this.cleanupByInstaller.has(installer)) return;
182
+ const cleanup = installer(this);
183
+ this.cleanupByInstaller.set(installer, cleanup);
184
+ this.cleanups.add(cleanup);
185
+ }
186
+ removeInstaller(installer) {
187
+ const cleanup = this.cleanupByInstaller.get(installer);
188
+ if (!cleanup) return;
189
+ this.cleanupByInstaller.delete(installer);
190
+ this.cleanups.delete(cleanup);
191
+ cleanup();
192
+ }
193
+ assertSamePair(config) {
194
+ if (config.virentia !== this.virentia || config.effector !== this.effector) throw new Error("Effector compatibility association is already bound to another scope pair");
195
+ }
196
+ launchEffector(unit, payload, options = {}) {
197
+ this.assertAlive();
198
+ if (options.suppressWatch) this.incrementSuppression(this.suppressedEffector, unit);
199
+ try {
200
+ launch({
201
+ target: unit,
202
+ params: payload,
203
+ scope: this.effector
415
204
  });
416
- return prepended;
205
+ } finally {
206
+ if (options.suppressWatch) this.decrementSuppression(this.suppressedEffector, unit);
417
207
  }
208
+ }
209
+ emitVirentia(unit, payload, options = {}) {
210
+ this.assertAlive();
211
+ if (options.suppressReaction) this.incrementSuppression(this.suppressedVirentia, unit);
212
+ const settled = virentia.allSettled(unit, {
213
+ payload,
214
+ scope: this.virentia
215
+ });
216
+ if (options.suppressReaction) settled.finally(() => {
217
+ this.decrementSuppression(this.suppressedVirentia, unit);
218
+ });
219
+ }
220
+ shouldSkipEffector(unit) {
221
+ return (this.suppressedEffector.get(unit) ?? 0) > 0;
222
+ }
223
+ shouldSkipVirentia(unit) {
224
+ return (this.suppressedVirentia.get(unit) ?? 0) > 0;
225
+ }
226
+ assertAlive() {
227
+ if (this.disposed) throw new Error("Effector compatibility association is disposed");
228
+ }
229
+ incrementSuppression(map, unit) {
230
+ map.set(unit, (map.get(unit) ?? 0) + 1);
231
+ }
232
+ decrementSuppression(map, unit) {
233
+ const next = (map.get(unit) ?? 0) - 1;
234
+ if (next <= 0) map.delete(unit);
235
+ else map.set(unit, next);
236
+ }
237
+ };
238
+ function createEffectorScopeNode(unit, fn) {
239
+ const node = createNode({
240
+ parent: [unit],
241
+ node: [...is.store(unit) ? [step.mov({
242
+ store: unit.stateRef,
243
+ to: "stack"
244
+ })] : [], step.run({ fn(payload, _local, stack) {
245
+ fn(payload, stack.scope);
246
+ } })],
247
+ family: { owners: [unit] }
418
248
  });
419
- const use = ((nextHandler) => {
420
- handler = nextHandler;
421
- return result;
422
- });
423
- use.getCurrent = () => {
424
- if (!handler) throw new Error("Effect handler is not defined");
425
- return handler;
249
+ return () => {
250
+ clearNode(node);
426
251
  };
427
- result.use = use;
428
- return result;
429
252
  }
430
- //#endregion
431
- //#region lib/attach.ts
432
- function attach(config) {
433
- return createEffect({
434
- name: config.name ?? getName(config.effect),
435
- handler: (params) => {
436
- const hasSource = config.source !== void 0;
437
- const sourceValue = hasSource ? readSource(config.source) : void 0;
438
- if (isEffect(config.effect)) {
439
- const nextParams = config.mapParams ? config.mapParams(params, sourceValue) : params;
440
- return config.effect.use.getCurrent()(nextParams);
253
+ function installLink(runtime, definition) {
254
+ const map = definition.map ?? identity;
255
+ if (isEffectorUnit(definition.from)) return toUnsubscribe(createWatch({
256
+ unit: definition.from,
257
+ scope: runtime.effector,
258
+ fn(payload) {
259
+ if (runtime.shouldSkipEffector(definition.from)) return;
260
+ const nextPayload = map(payload);
261
+ if (isEffectorUnit(definition.to)) {
262
+ runtime.launchEffector(definition.to, nextPayload);
263
+ return;
441
264
  }
442
- return hasSource ? config.effect(sourceValue, params) : config.effect(params);
265
+ runtime.emitVirentia(definition.to, nextPayload, { suppressReaction: true });
443
266
  }
444
- });
445
- }
446
- //#endregion
447
- //#region lib/persistence.ts
448
- function serialize(scope, config = {}) {
449
- const compatScope = createCompatScope(scope.__core);
450
- const onlyChanges = config.onlyChanges ?? true;
451
- const ignored = new Set((config.ignore ?? []).map((item) => typeof item === "string" ? item : item.sid).filter((sid) => typeof sid === "string"));
452
- const result = {};
453
- for (const [sid, store] of storesBySid) {
454
- if (ignored.has(sid)) continue;
455
- if (onlyChanges && !compatScope.__changedSids.has(sid)) continue;
456
- result[sid] = store.getState(compatScope);
267
+ }));
268
+ if (isVirentiaUnit(definition.from)) {
269
+ const watcher = virentia.reaction({
270
+ on: definition.from,
271
+ scope: runtime.virentia,
272
+ run(payload) {
273
+ if (runtime.shouldSkipVirentia(definition.from)) return;
274
+ const nextPayload = map(payload);
275
+ if (isEffectorUnit(definition.to)) {
276
+ runtime.launchEffector(definition.to, nextPayload, { suppressWatch: true });
277
+ return;
278
+ }
279
+ runtime.emitVirentia(definition.to, nextPayload);
280
+ }
281
+ });
282
+ return () => {
283
+ watcher.stop();
284
+ };
457
285
  }
458
- return result;
286
+ throw new Error("Effector compatibility link expects Effector or Virentia units");
459
287
  }
460
- function hydrate(scope, config) {
461
- applyStoreValues(scope, config.values);
288
+ function createMissingRuntimeError(config) {
289
+ if (config.effector) return /* @__PURE__ */ new Error("Effector compatibility association is missing for provided Effector scope");
290
+ if (config.virentia) return /* @__PURE__ */ new Error("Effector compatibility association is missing for provided Virentia scope");
291
+ return /* @__PURE__ */ new Error("Effector compatibility association is missing. Call associate({ virentia, effector }) before using adapters.");
462
292
  }
463
- //#endregion
464
- //#region lib/scope.ts
465
- function fork(config) {
466
- const nextScope = createCompatScope(core.scope());
467
- if (config?.values) hydrate(nextScope, { values: config.values });
468
- return nextScope;
293
+ function isEffectorUnit(value) {
294
+ return is.unit(value);
469
295
  }
470
- async function allSettled(unitOrScope, options = {}) {
471
- if (isScope(unitOrScope)) return;
472
- const scope = options.scope ?? createCompatScope(defaultScope);
473
- const unit = unitOrScope;
474
- if (isStore(unit)) {
475
- unit.setState(options.params, scope);
476
- return;
477
- }
478
- if (isEffect(unit)) try {
479
- return {
480
- status: "done",
481
- value: await core.scoped(scope.__core, () => unit(options.params))
482
- };
483
- } catch (value) {
484
- return {
485
- status: "fail",
486
- value
487
- };
488
- }
489
- await core.allSettled(unit.__core, {
490
- scope: scope.__core,
491
- payload: options.params
492
- });
296
+ function isVirentiaUnit(value) {
297
+ return Boolean(value && (typeof value === "object" || typeof value === "function") && "node" in value && !isEffectorUnit(value));
298
+ }
299
+ function isVirentiaEffect(value) {
300
+ return Boolean(isVirentiaUnit(value) && "doneData" in value && "$pending" in value);
301
+ }
302
+ function toUnsubscribe(subscription) {
303
+ return () => {
304
+ subscription.unsubscribe();
305
+ };
493
306
  }
494
- function scopeBind(unit, config) {
495
- const scope = config?.scope ?? createCompatScope(defaultScope);
496
- return (...payload) => core.scoped(scope.__core, () => unit(...payload));
307
+ function identity(value) {
308
+ return value;
497
309
  }
498
310
  //#endregion
499
- export { allSettled, attach, combine, createApi, createEffect, createEvent, createStore, fork, hydrate, is, restore, sample, scopeBind, serialize, split };
311
+ export { createEffectorCompatibility };