@volynets/reflex 0.1.0 → 0.1.2

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.
@@ -0,0 +1,90 @@
1
+ import { readConsumerEager, readConsumerLazy } from "@reflex/runtime";
2
+ import { createComputedNode } from "../infra";
3
+
4
+ /**
5
+ * Creates a lazy derived accessor.
6
+ *
7
+ * `computed` runs `fn` only when the returned accessor is read. During that
8
+ * evaluation it tracks the reactive values that `fn` touches, caches the
9
+ * result, and reuses the cached value for subsequent clean reads.
10
+ *
11
+ * @typeParam T - Derived value type.
12
+ *
13
+ * @param fn - Pure synchronous computation that derives a value from reactive
14
+ * reads.
15
+ *
16
+ * @returns Tracked accessor that returns the latest derived value.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * createRuntime();
21
+ *
22
+ * const [count, setCount] = signal(1);
23
+ * const doubled = computed(() => count() * 2);
24
+ *
25
+ * console.log(doubled()); // 2
26
+ *
27
+ * setCount(2);
28
+ *
29
+ * console.log(doubled()); // 4
30
+ * ```
31
+ *
32
+ * @remarks
33
+ * - `fn` does not run until the first read.
34
+ * - Dependencies are tracked dynamically on each execution, so branch changes
35
+ * automatically update the dependency set.
36
+ * - Dirty computeds recompute on demand when read again.
37
+ * - Reading a computed does not require `rt.flush()`; `flush()` is only for
38
+ * scheduled effects.
39
+ * - Keep `fn` pure and synchronous.
40
+ *
41
+ * @see memo
42
+ * @see effect
43
+ */
44
+ export function computed<T>(fn: () => T): Accessor<T> {
45
+ const node = createComputedNode(fn);
46
+ return readConsumerLazy.bind(null, node) as Accessor<T>;
47
+ }
48
+
49
+ /**
50
+ * Creates a computed accessor and warms it eagerly once.
51
+ *
52
+ * `memo` has the same dependency tracking and caching semantics as
53
+ * `computed()`, but it performs one eager read immediately after creation.
54
+ * This is useful when you want the initial value materialized up front while
55
+ * still interacting with a normal accessor afterward.
56
+ *
57
+ * @typeParam T - Derived value type.
58
+ *
59
+ * @param fn - Pure synchronous computation that derives a value from reactive
60
+ * reads.
61
+ *
62
+ * @returns Tracked accessor that returns the latest memoized value.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * createRuntime();
67
+ *
68
+ * const [price, setPrice] = signal(100);
69
+ * const total = memo(() => price() * 1.2);
70
+ *
71
+ * console.log(total()); // 120
72
+ *
73
+ * setPrice(200);
74
+ *
75
+ * console.log(total()); // 240
76
+ * ```
77
+ *
78
+ * @remarks
79
+ * - `memo(fn)` is equivalent to `computed(fn)` plus one immediate warm-up read.
80
+ * - After the warm-up, clean reads reuse the cached value exactly like
81
+ * `computed()`.
82
+ * - Later invalidations still follow normal computed semantics.
83
+ *
84
+ * @see computed
85
+ */
86
+ export function memo<T>(fn: () => T): Accessor<T> {
87
+ const node = createComputedNode(fn);
88
+ readConsumerEager(node);
89
+ return readConsumerLazy.bind(null, node) as Accessor<T>;
90
+ }
@@ -0,0 +1,120 @@
1
+ import {
2
+ disposeWatcher,
3
+ getDefaultContext,
4
+ ReactiveNodeState,
5
+ runWatcher,
6
+ } from "@reflex/runtime";
7
+ import type { ReactiveNode } from "@reflex/runtime";
8
+ import type { UNINITIALIZED } from "../infra/factory";
9
+ import { createWatcherNode } from "../infra/factory";
10
+
11
+ /**
12
+ * Marks an effect watcher node as scheduled.
13
+ *
14
+ * This is a low-level helper used by scheduler integrations and tests to set
15
+ * the runtime's scheduled flag on a watcher node.
16
+ */
17
+ export function effectScheduled(
18
+ node: ReactiveNode<typeof UNINITIALIZED | Destructor>,
19
+ ) {
20
+ node.state |= ReactiveNodeState.Scheduled;
21
+ }
22
+
23
+ /**
24
+ * Clears the scheduled flag from an effect watcher node.
25
+ *
26
+ * This is a low-level helper used by scheduler integrations and tests to mark
27
+ * a watcher as no longer queued for execution.
28
+ */
29
+ export function effectUnscheduled(
30
+ node: ReactiveNode<typeof UNINITIALIZED | Destructor>,
31
+ ) {
32
+ node.state &= ~ReactiveNodeState.Scheduled;
33
+ }
34
+
35
+ /**
36
+ * Callback used to register cleanup produced by nested helpers with an
37
+ * enclosing effect scope.
38
+ */
39
+ export type EffectCleanupRegistrar = (cleanup: Destructor) => void;
40
+
41
+ /**
42
+ * Runs `fn` with a temporary cleanup registrar installed on the active runtime
43
+ * context.
44
+ *
45
+ * Helpers that allocate resources during `fn` can forward their teardown to
46
+ * `registrar`, allowing the surrounding effect or integration to dispose them
47
+ * automatically.
48
+ *
49
+ * @typeParam T - Return type of `fn`.
50
+ *
51
+ * @param registrar - Cleanup registrar to expose during `fn`, or `null` to run
52
+ * without one.
53
+ * @param fn - Callback executed with the temporary registrar installed.
54
+ *
55
+ * @returns The value returned by `fn`.
56
+ *
57
+ * @remarks
58
+ * - The registrar is scoped to the duration of `fn`.
59
+ * - This is a low-level integration helper. Most application code should use
60
+ * `effect()` directly.
61
+ */
62
+ export function withEffectCleanupRegistrar<T>(
63
+ registrar: EffectCleanupRegistrar | null,
64
+ fn: () => T,
65
+ ): T {
66
+ const context = getDefaultContext();
67
+ return context.withCleanupRegistrar(registrar, fn);
68
+ }
69
+
70
+ /**
71
+ * Creates a reactive effect.
72
+ *
73
+ * `effect` runs `fn` immediately, tracks any reactive values read during that
74
+ * run, and schedules re-execution when those dependencies change.
75
+ *
76
+ * @param fn - Effect body. It may return a cleanup function that runs before
77
+ * the next execution and when the effect is disposed.
78
+ *
79
+ * @returns Destructor that disposes the effect and runs the latest cleanup, if
80
+ * present.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const rt = createRuntime();
85
+ * const [count, setCount] = signal(0);
86
+ *
87
+ * const stop = effect(() => {
88
+ * console.log(count());
89
+ * });
90
+ *
91
+ * setCount(1);
92
+ * rt.flush();
93
+ *
94
+ * stop();
95
+ * ```
96
+ *
97
+ * @remarks
98
+ * - The first run happens synchronously during `effect()` creation.
99
+ * - With the default runtime strategy, later re-runs are queued until
100
+ * `rt.flush()`.
101
+ * - With `createRuntime({ effectStrategy: "sab" })`, invalidations stay lazy
102
+ * during propagation but auto-deliver after the outermost `rt.batch()`.
103
+ * - With `createRuntime({ effectStrategy: "eager" })`, invalidations flush
104
+ * automatically.
105
+ * - Reads performed inside cleanup do not become dependencies of the next run.
106
+ * - Disposing the returned scope prevents future re-runs.
107
+ *
108
+ * @see createRuntime
109
+ * @see computed
110
+ * @see memo
111
+ */
112
+ export function effect(fn: EffectFn): Destructor {
113
+ const context = getDefaultContext();
114
+ const node = createWatcherNode(fn);
115
+ runWatcher(node);
116
+
117
+ const dispose = disposeWatcher.bind(null, node) as Destructor;
118
+ context.registerWatcherCleanup(dispose);
119
+ return dispose;
120
+ }
@@ -0,0 +1,257 @@
1
+ import {
2
+ disposeNodeEvent,
3
+ isDisposedNode,
4
+ readProducer,
5
+ writeProducer,
6
+ } from "@reflex/runtime";
7
+ import type { Event } from "../infra";
8
+ import { createAccumulator } from "../infra";
9
+
10
+ type EventValue<E extends Event<unknown>> =
11
+ E extends Event<infer T> ? T : never;
12
+
13
+ function createEvent<T>(
14
+ subscribe: (fn: (value: T) => void) => Destructor,
15
+ ): Event<T> {
16
+ return { subscribe };
17
+ }
18
+
19
+ /**
20
+ * Subscribes to the first value from `source`, then unsubscribes automatically.
21
+ *
22
+ * The subscription is disposed before `fn` runs, so nested emits triggered from
23
+ * inside `fn` will not deliver a second time to the same callback.
24
+ */
25
+ export function subscribeOnce<T>(
26
+ source: Event<T>,
27
+ fn: (value: T) => void,
28
+ ): Destructor {
29
+ let active = true;
30
+ let unsubscribe: Destructor | undefined;
31
+ let unsubscribePending = false;
32
+
33
+ const dispose = () => {
34
+ if (!active) return;
35
+
36
+ active = false;
37
+
38
+ const stop = unsubscribe;
39
+ if (stop === undefined) {
40
+ unsubscribePending = true;
41
+ return;
42
+ }
43
+
44
+ unsubscribe = undefined;
45
+ stop();
46
+ };
47
+
48
+ unsubscribe = source.subscribe((value) => {
49
+ if (!active) return;
50
+
51
+ dispose();
52
+ fn(value);
53
+ });
54
+
55
+ if (unsubscribePending) {
56
+ const stop = unsubscribe;
57
+ unsubscribe = undefined;
58
+ stop?.();
59
+ }
60
+
61
+ return dispose;
62
+ }
63
+
64
+ /**
65
+ * Projects each event value from `source` into a new event stream.
66
+ */
67
+ export function map<T, U>(
68
+ source: Event<T>,
69
+ project: (value: T) => U,
70
+ ): Event<U> {
71
+ return createEvent((fn) =>
72
+ source.subscribe((value) => {
73
+ fn(project(value));
74
+ }),
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Forwards only the values from `source` that satisfy `predicate`.
80
+ */
81
+ export function filter<T, S extends T>(
82
+ source: Event<T>,
83
+ predicate: (value: T) => value is S,
84
+ ): Event<S>;
85
+ export function filter<T>(
86
+ source: Event<T>,
87
+ predicate: (value: T) => boolean,
88
+ ): Event<T>;
89
+ export function filter<T>(
90
+ source: Event<T>,
91
+ predicate: (value: T) => boolean,
92
+ ): Event<T> {
93
+ return createEvent((fn) =>
94
+ source.subscribe((value) => {
95
+ if (predicate(value)) {
96
+ fn(value);
97
+ }
98
+ }),
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Merges multiple event sources into one event stream.
104
+ *
105
+ * The resulting event preserves the delivery order defined by the upstream
106
+ * sources and their runtime dispatcher.
107
+ */
108
+ export function merge<const Sources extends readonly Event<unknown>[]>(
109
+ ...sources: Sources
110
+ ): Event<EventValue<Sources[number]>> {
111
+ return createEvent((fn) => {
112
+ const unsubscribers = sources.map((source) =>
113
+ source.subscribe((value) => {
114
+ fn(value as EventValue<Sources[number]>);
115
+ }),
116
+ );
117
+
118
+ return () => {
119
+ for (const unsubscribe of unsubscribers) {
120
+ unsubscribe();
121
+ }
122
+ };
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Creates an accumulator derived from an event stream.
128
+ *
129
+ * `scan` listens to `source` and applies `reducer` to the current accumulated
130
+ * state and each incoming event value. The result becomes the next stored state.
131
+ *
132
+ * It is analogous to `Array.prototype.reduce`, but for a stream of events over time.
133
+ *
134
+ * @typeParam T - Event payload type.
135
+ * @typeParam A - Accumulator state type.
136
+ *
137
+ * @param source - Event source to subscribe to.
138
+ * @param seed - Initial accumulator state used before the first event arrives.
139
+ * @param reducer - Pure function that receives the current accumulated state and
140
+ * the next event value, and returns the next accumulated state.
141
+ *
142
+ * @returns A tuple:
143
+ * - `read` - accessor that returns the current accumulated state.
144
+ * - `dispose` - destructor that unsubscribes from the source and disposes the internal node.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const rt = createRuntime();
149
+ * const increments = rt.event<number>();
150
+ *
151
+ * const [total, dispose] = scan(increments, 0, (acc, value) => acc + value);
152
+ *
153
+ * increments.emit(1);
154
+ * increments.emit(2);
155
+ *
156
+ * console.log(total()); // 3
157
+ *
158
+ * dispose();
159
+ * ```
160
+ *
161
+ * @remarks
162
+ * - `seed` is used as the initial state until the first event is delivered.
163
+ * - `reducer` should be pure and synchronous.
164
+ * - `reducer` should derive the next state only from the previous accumulated
165
+ * state and the current event value.
166
+ * - The accumulated value is updated only in response to `source` events.
167
+ * - Do not read signals, computeds, or other reactive values inside
168
+ * `reducer`. `scan` does not track reactive dependencies read there.
169
+ * - If you need to combine event-driven state with reactive state, first
170
+ * derive the accumulator with `scan`, then combine it outside via
171
+ * `computed()`.
172
+ * - To stop receiving updates and release subscriptions, call `dispose`.
173
+ *
174
+ * @see hold
175
+ */
176
+ export function scan<T, A>(
177
+ source: Event<T>,
178
+ seed: A,
179
+ reducer: (acc: A, value: T) => A,
180
+ ): [read: Accessor<A>, dispose: Destructor] {
181
+ return createScan(source, seed, reducer);
182
+ }
183
+
184
+ /**
185
+ * Stores the latest value emitted by an event source.
186
+ *
187
+ * `hold` is a specialized form of {@link scan} that replaces the current state
188
+ * with each new event value.
189
+ *
190
+ * @typeParam T - Event payload type.
191
+ *
192
+ * @param source - Event source to subscribe to.
193
+ * @param initial - Initial value returned before the first event arrives.
194
+ *
195
+ * @returns A tuple:
196
+ * - `read` - accessor that returns the latest observed event value.
197
+ * - `dispose` - destructor that unsubscribes from the source and disposes the internal node.
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * const rt = createRuntime();
202
+ * const updates = rt.event<string>();
203
+ *
204
+ * const [latest, dispose] = hold(updates, "idle");
205
+ *
206
+ * console.log(latest()); // "idle"
207
+ *
208
+ * updates.emit("loading");
209
+ * console.log(latest()); // "loading"
210
+ *
211
+ * updates.emit("done");
212
+ * console.log(latest()); // "done"
213
+ *
214
+ * dispose();
215
+ * ```
216
+ *
217
+ * @remarks
218
+ * - `initial` is returned until the first event is delivered.
219
+ * - Equivalent to:
220
+ * `scan(source, initial, (_, value) => value)`
221
+ *
222
+ * @see scan
223
+ */
224
+ export function hold<T>(
225
+ source: Event<T>,
226
+ initial: T,
227
+ ): [read: Accessor<T>, dispose: Destructor] {
228
+ return createScan(source, initial, (_, value) => value);
229
+ }
230
+
231
+ function createScan<T, A>(
232
+ source: Event<T>,
233
+ seed: A,
234
+ reducer: (acc: A, value: T) => A,
235
+ ): [read: Accessor<A>, dispose: Destructor] {
236
+ const node = createAccumulator(seed);
237
+ let current = seed;
238
+ const accessor = () => (isDisposedNode(node) ? current : readProducer(node));
239
+
240
+ let unsubscribe: Destructor | undefined = source.subscribe((value: T) => {
241
+ /* c8 ignore start -- disposal unsubscribes before a queued delivery can reach this callback */
242
+ if (isDisposedNode(node)) return;
243
+ /* c8 ignore stop */
244
+ current = reducer(current, value);
245
+ writeProducer(node, current);
246
+ });
247
+
248
+ function dispose(): void {
249
+ disposeNodeEvent(node);
250
+
251
+ const stop = unsubscribe;
252
+ unsubscribe = undefined;
253
+ stop?.();
254
+ }
255
+
256
+ return [accessor, dispose];
257
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./derived";
2
+ export * from "./effect";
3
+ export * from "./event";
4
+ export * from "./signal";
@@ -0,0 +1,68 @@
1
+ import { readProducer, writeProducer } from "@reflex/runtime";
2
+ import { createSignalNode } from "../infra";
3
+
4
+ /**
5
+ * Creates writable reactive state.
6
+ *
7
+ * `signal` returns a tuple containing a tracked read accessor and a setter.
8
+ * Reading the accessor inside `computed()`, `memo()`, or `effect()` registers
9
+ * a dependency. Writing through the setter updates the stored value
10
+ * synchronously and invalidates downstream reactive consumers only when the
11
+ * value actually changes.
12
+ *
13
+ * @typeParam T - Signal value type.
14
+ *
15
+ * @param initialValue - Initial signal value returned until a later write
16
+ * replaces it.
17
+ * @param options - Optional development diagnostics. `options.name` is used
18
+ * only in development builds when formatting setter error messages.
19
+ *
20
+ * @returns A readonly tuple:
21
+ * - `value` - tracked accessor that returns the current signal value.
22
+ * - `setValue` - setter that accepts either a direct value or an updater
23
+ * function receiving the previous value. The setter returns the committed
24
+ * next value.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * createRuntime();
29
+ *
30
+ * const [count, setCount] = signal(0);
31
+ *
32
+ * console.log(count()); // 0
33
+ *
34
+ * setCount(1);
35
+ * setCount((prev) => prev + 1);
36
+ *
37
+ * console.log(count()); // 2
38
+ * ```
39
+ *
40
+ * @remarks
41
+ * - Reads are synchronous and always return the latest committed value.
42
+ * - Same-value writes do not invalidate downstream computed values or effects.
43
+ * - Calling `setValue()` with no argument is only valid when `T` includes
44
+ * `undefined`.
45
+ * - In typical app code, call `createRuntime()` during setup before building
46
+ * the rest of the reactive graph.
47
+ *
48
+ * @see computed
49
+ * @see memo
50
+ * @see effect
51
+ */
52
+ export function signal<T>(initialValue: T): readonly [Accessor<T>, Setter<T>] {
53
+ const node = createSignalNode(initialValue);
54
+
55
+ function set(input: SetInput<T>) {
56
+ const payload = node.payload;
57
+ const next =
58
+ typeof input === "function"
59
+ ? (input as (prev: T) => T)(payload as T)
60
+ : input;
61
+ writeProducer(node, next);
62
+ }
63
+
64
+ return [
65
+ readProducer.bind(null, node) as Accessor<T>,
66
+ set as Setter<T>,
67
+ ] as const;
68
+ }
@@ -0,0 +1,169 @@
1
+ declare const __DEV__: boolean;
2
+
3
+ /**
4
+ * Cleanup function returned from an effect.
5
+ */
6
+ type Destructor = () => void;
7
+
8
+ /**
9
+ * Effect callback.
10
+ * May return a cleanup function.
11
+ */
12
+ type EffectFn = () => void | Destructor;
13
+
14
+ type AnyFn = (...args: unknown[]) => unknown;
15
+
16
+ /**
17
+ * Direct value that can be assigned via `.set(value)`.
18
+ * Function values are excluded to avoid ambiguity with updater functions.
19
+ */
20
+ type DirectValue<T> = Exclude<T, AnyFn>;
21
+
22
+ /**
23
+ * Functional updater.
24
+ */
25
+ type Updater<T> = (prev: T) => T;
26
+
27
+ /**
28
+ * Accepted input for writable reactive values.
29
+ */
30
+ type SetInput<T> = DirectValue<T> | Updater<T>;
31
+
32
+ interface RequiredSetter<T> {
33
+ (value: SetInput<T>): T;
34
+ }
35
+
36
+ interface OptionalSetter<T> {
37
+ (): T;
38
+ (value: SetInput<T>): T;
39
+ }
40
+
41
+ /**
42
+ * If T includes undefined, calling `set()` with no arguments is allowed.
43
+ */
44
+ type Setter<T> = undefined extends T ? OptionalSetter<T> : RequiredSetter<T>;
45
+
46
+ /**
47
+ * Nominal brand helper for semantically distinct reactive primitives.
48
+ */
49
+ interface Brand<K extends string> {
50
+ readonly __brand?: K;
51
+ }
52
+
53
+ /**
54
+ * Callable tracked read.
55
+ */
56
+ interface Accessor<T> {
57
+ (): T;
58
+ }
59
+
60
+ /**
61
+ * Property-based read.
62
+ */
63
+ interface ValueReadable<T> {
64
+ readonly value: T;
65
+ }
66
+
67
+ /**
68
+ * Untracked read.
69
+ */
70
+ interface Peekable<T> {
71
+ peek(): T;
72
+ }
73
+
74
+ /**
75
+ * Writable capability.
76
+ */
77
+ interface Writable<T> {
78
+ set: Setter<T>;
79
+ }
80
+
81
+ interface Disposable {
82
+ (): void;
83
+ }
84
+
85
+ /**
86
+ * Standard readable reactive value.
87
+ */
88
+ interface Readable<T> extends Accessor<T>, ValueReadable<T> {}
89
+
90
+ /**
91
+ * Readable value with untracked read.
92
+ */
93
+ interface PeekableReadable<T> extends Readable<T>, Peekable<T> {}
94
+
95
+ /**
96
+ * Writable signal-like value.
97
+ */
98
+ interface WritableReadable<T> extends Readable<T>, Writable<T> {}
99
+
100
+ /**
101
+ * Writable signal-like value with untracked read.
102
+ */
103
+ interface PeekableWritableReadable<T>
104
+ extends WritableReadable<T>,
105
+ Peekable<T> {}
106
+
107
+ /**
108
+ * Mutable signal.
109
+ */
110
+ interface Signal<T> extends PeekableWritableReadable<T>, Brand<"signal"> {}
111
+
112
+ /**
113
+ * Computed reactive value.
114
+ */
115
+ interface Computed<T> extends PeekableReadable<T>, Brand<"computed"> {}
116
+
117
+ /**
118
+ * Memoized derived value.
119
+ */
120
+ interface Memo<T> extends PeekableReadable<T>, Brand<"memo"> {}
121
+
122
+ /**
123
+ * Derived reactive value.
124
+ */
125
+ interface Derived<T> extends PeekableReadable<T>, Brand<"derived"> {}
126
+
127
+ interface Effect<T> extends Readable<T>, Brand<"effect">, Disposable {}
128
+
129
+ interface Scan<T> extends Readable<T>, Brand<"scan">, Disposable {}
130
+
131
+ /**
132
+ * Push-based realtime source.
133
+ */
134
+ interface Realtime<T> extends PeekableWritableReadable<T>, Brand<"realtime"> {
135
+ subscribe(cb: () => void): () => void;
136
+ }
137
+
138
+ /**
139
+ * Async iterable stream source.
140
+ */
141
+ interface Stream<T> extends PeekableWritableReadable<T>, Brand<"stream"> {
142
+ [Symbol.asyncIterator](): AsyncIterator<T>;
143
+ }
144
+
145
+ /**
146
+ * Common readonly view over reactive values.
147
+ */
148
+ type ReadableLike<T> =
149
+ | Signal<T>
150
+ | Computed<T>
151
+ | Memo<T>
152
+ | Derived<T>
153
+ | Realtime<T>
154
+ | Stream<T>;
155
+
156
+ /**
157
+ * Common writable view over reactive values.
158
+ */
159
+ type WritableLike<T> = Signal<T> | Realtime<T> | Stream<T>;
160
+
161
+ /**
162
+ * Extract value type from a reactive value.
163
+ */
164
+ type ValueOf<T> =
165
+ T extends Accessor<infer V>
166
+ ? V
167
+ : T extends ValueReadable<infer V>
168
+ ? V
169
+ : never;
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
+ /// <reference path="./globals.d.ts" />
3
+
4
+ export { signal, computed, memo, effect, withEffectCleanupRegistrar } from "./api";
5
+ export { subscribeOnce, map, filter, merge, scan, hold } from "./api";
6
+ export { createRuntime } from "./infra";