@volynets/reflex 0.1.1 → 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.
package/dist/globals.d.ts CHANGED
@@ -171,7 +171,84 @@ declare global {
171
171
  const __DEV__: boolean;
172
172
  }
173
173
 
174
+ /**
175
+ * Creates a lazy derived accessor.
176
+ *
177
+ * `computed` runs `fn` only when the returned accessor is read. During that
178
+ * evaluation it tracks the reactive values that `fn` touches, caches the
179
+ * result, and reuses the cached value for subsequent clean reads.
180
+ *
181
+ * @typeParam T - Derived value type.
182
+ *
183
+ * @param fn - Pure synchronous computation that derives a value from reactive
184
+ * reads.
185
+ *
186
+ * @returns Tracked accessor that returns the latest derived value.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * createRuntime();
191
+ *
192
+ * const [count, setCount] = signal(1);
193
+ * const doubled = computed(() => count() * 2);
194
+ *
195
+ * console.log(doubled()); // 2
196
+ *
197
+ * setCount(2);
198
+ *
199
+ * console.log(doubled()); // 4
200
+ * ```
201
+ *
202
+ * @remarks
203
+ * - `fn` does not run until the first read.
204
+ * - Dependencies are tracked dynamically on each execution, so branch changes
205
+ * automatically update the dependency set.
206
+ * - Dirty computeds recompute on demand when read again.
207
+ * - Reading a computed does not require `rt.flush()`; `flush()` is only for
208
+ * scheduled effects.
209
+ * - Keep `fn` pure and synchronous.
210
+ *
211
+ * @see memo
212
+ * @see effect
213
+ */
174
214
  declare function computed<T>(fn: () => T): Accessor<T>;
215
+ /**
216
+ * Creates a computed accessor and warms it eagerly once.
217
+ *
218
+ * `memo` has the same dependency tracking and caching semantics as
219
+ * `computed()`, but it performs one eager read immediately after creation.
220
+ * This is useful when you want the initial value materialized up front while
221
+ * still interacting with a normal accessor afterward.
222
+ *
223
+ * @typeParam T - Derived value type.
224
+ *
225
+ * @param fn - Pure synchronous computation that derives a value from reactive
226
+ * reads.
227
+ *
228
+ * @returns Tracked accessor that returns the latest memoized value.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * createRuntime();
233
+ *
234
+ * const [price, setPrice] = signal(100);
235
+ * const total = memo(() => price() * 1.2);
236
+ *
237
+ * console.log(total()); // 120
238
+ *
239
+ * setPrice(200);
240
+ *
241
+ * console.log(total()); // 240
242
+ * ```
243
+ *
244
+ * @remarks
245
+ * - `memo(fn)` is equivalent to `computed(fn)` plus one immediate warm-up read.
246
+ * - After the warm-up, clean reads reuse the cached value exactly like
247
+ * `computed()`.
248
+ * - Later invalidations still follow normal computed semantics.
249
+ *
250
+ * @see computed
251
+ */
175
252
  declare function memo<T>(fn: () => T): Accessor<T>;
176
253
 
177
254
  /**
@@ -207,14 +284,14 @@ interface Reactivable {
207
284
  type ComputeFn<T> = (() => T) | null;
208
285
  declare class ReactiveNode<T = unknown> implements Reactivable {
209
286
  state: number;
210
- compute: ComputeFn<T>;
211
287
  firstOut: ReactiveEdge | null;
212
288
  firstIn: ReactiveEdge | null;
213
289
  lastOut: ReactiveEdge | null;
214
290
  lastIn: ReactiveEdge | null;
215
291
  depsTail: ReactiveEdge | null;
292
+ compute: ComputeFn<T>;
216
293
  payload: T;
217
- constructor(payload: T | undefined, compute: ComputeFn<T>, state: number);
294
+ constructor(payload: T, compute: ComputeFn<T>, state: number);
218
295
  }
219
296
 
220
297
  /**
@@ -223,13 +300,14 @@ declare class ReactiveNode<T = unknown> implements Reactivable {
223
300
  * incoming list, so pointer rewrites must keep both views in sync.
224
301
  */
225
302
  declare class ReactiveEdge {
226
- from: ReactiveNode;
227
- to: ReactiveNode;
303
+ version: number;
228
304
  prevOut: ReactiveEdge | null;
229
305
  nextOut: ReactiveEdge | null;
306
+ from: ReactiveNode;
307
+ to: ReactiveNode;
230
308
  prevIn: ReactiveEdge | null;
231
309
  nextIn: ReactiveEdge | null;
232
- constructor(from: ReactiveNode, to: ReactiveNode, prevOut: ReactiveEdge | null, nextOut: ReactiveEdge | null, prevIn: ReactiveEdge | null, nextIn: ReactiveEdge | null);
310
+ constructor(version: number, prevOut: ReactiveEdge | null, nextOut: ReactiveEdge | null, from: ReactiveNode, to: ReactiveNode, prevIn: ReactiveEdge | null, nextIn: ReactiveEdge | null);
233
311
  }
234
312
 
235
313
  interface EngineHooks {
@@ -237,63 +315,241 @@ interface EngineHooks {
237
315
  onReactiveSettled?(): void;
238
316
  }
239
317
  type CleanupRegistrar = (cleanup: () => void) => void;
318
+ type TrackReadFallback = (source: ReactiveNode, consumer: ReactiveNode, prev: ReactiveEdge | null, nextExpected: ReactiveEdge | null, version: number) => ReactiveEdge;
319
+ interface ExecutionContextOptions {
320
+ trackReadFallback?: TrackReadFallback;
321
+ }
240
322
  type OnEffectInvalidatedHook = EngineHooks["onEffectInvalidated"];
241
323
  type OnReactiveSettledHook = EngineHooks["onReactiveSettled"];
242
- /**
243
- * ExecutionContext управляет состоянием вычисления и уведомлениями host'у.
244
- *
245
- * Ключевые принципы:
246
- * - Контекст НЕ глобальный - это объект, передаваемый по параметрам
247
- * - Host полностью контролирует scheduling эффектов
248
- * - Контекст только отслеживает текущее состояние вычисления
249
- *
250
- * Поля:
251
- * - activeComputed: текущий узел в процессе вычисления (для trackRead)
252
- * - propagationDepth: глубина каскада инвалидаций
253
- * - cleanupRegistrar: функция для регистрации cleanup в эффектах
254
- */
255
324
  declare class ExecutionContext {
256
325
  activeComputed: ReactiveNode | null;
326
+ trackingVersion: number;
257
327
  propagationDepth: number;
258
328
  cleanupRegistrar: CleanupRegistrar | null;
259
- readonly hooks: EngineHooks;
260
- onEffectInvalidatedHook: OnEffectInvalidatedHook;
261
- onReactiveSettledHook: OnReactiveSettledHook;
262
- private hookMask;
263
- constructor(hooks?: EngineHooks);
329
+ trackReadFallback: TrackReadFallback;
330
+ onEffectInvalidated: OnEffectInvalidatedHook;
331
+ onReactiveSettled: OnReactiveSettledHook;
332
+ runtimeOnEffectInvalidated: OnEffectInvalidatedHook;
333
+ runtimeOnReactiveSettled: OnReactiveSettledHook;
334
+ effectInvalidatedDispatch: OnEffectInvalidatedHook;
335
+ settledDispatch: OnReactiveSettledHook;
336
+ constructor(hooks?: EngineHooks, options?: ExecutionContextOptions);
264
337
  dispatchWatcherEvent(node: ReactiveNode): void;
265
338
  maybeNotifySettled(): void;
266
339
  enterPropagation(): void;
267
340
  leavePropagation(): void;
268
341
  resetState(): void;
342
+ setOptions(options?: ExecutionContextOptions): void;
269
343
  setHooks(hooks?: EngineHooks): void;
344
+ setRuntimeHooks(onEffectInvalidated?: OnEffectInvalidatedHook, onReactiveSettled?: OnReactiveSettledHook): void;
270
345
  registerWatcherCleanup(cleanup: () => void): void;
271
346
  withCleanupRegistrar<T>(registrar: CleanupRegistrar | null, fn: () => T): T;
272
- private setOnEffectInvalidatedHook;
273
- private setOnReactiveSettledHook;
274
- private updateHookMask;
347
+ private refreshDispatchers;
275
348
  }
276
349
 
277
350
 
351
+ /**
352
+ * Callback used to register cleanup produced by nested helpers with an
353
+ * enclosing effect scope.
354
+ */
355
+ type EffectCleanupRegistrar = (cleanup: Destructor) => void;
356
+ /**
357
+ * Runs `fn` with a temporary cleanup registrar installed on the active runtime
358
+ * context.
359
+ *
360
+ * Helpers that allocate resources during `fn` can forward their teardown to
361
+ * `registrar`, allowing the surrounding effect or integration to dispose them
362
+ * automatically.
363
+ *
364
+ * @typeParam T - Return type of `fn`.
365
+ *
366
+ * @param registrar - Cleanup registrar to expose during `fn`, or `null` to run
367
+ * without one.
368
+ * @param fn - Callback executed with the temporary registrar installed.
369
+ *
370
+ * @returns The value returned by `fn`.
371
+ *
372
+ * @remarks
373
+ * - The registrar is scoped to the duration of `fn`.
374
+ * - This is a low-level integration helper. Most application code should use
375
+ * `effect()` directly.
376
+ */
377
+ declare function withEffectCleanupRegistrar<T>(registrar: EffectCleanupRegistrar | null, fn: () => T): T;
378
+ /**
379
+ * Creates a reactive effect.
380
+ *
381
+ * `effect` runs `fn` immediately, tracks any reactive values read during that
382
+ * run, and schedules re-execution when those dependencies change.
383
+ *
384
+ * @param fn - Effect body. It may return a cleanup function that runs before
385
+ * the next execution and when the effect is disposed.
386
+ *
387
+ * @returns Destructor that disposes the effect and runs the latest cleanup, if
388
+ * present.
389
+ *
390
+ * @example
391
+ * ```ts
392
+ * const rt = createRuntime();
393
+ * const [count, setCount] = signal(0);
394
+ *
395
+ * const stop = effect(() => {
396
+ * console.log(count());
397
+ * });
398
+ *
399
+ * setCount(1);
400
+ * rt.flush();
401
+ *
402
+ * stop();
403
+ * ```
404
+ *
405
+ * @remarks
406
+ * - The first run happens synchronously during `effect()` creation.
407
+ * - With the default runtime strategy, later re-runs are queued until
408
+ * `rt.flush()`.
409
+ * - With `createRuntime({ effectStrategy: "sab" })`, invalidations stay lazy
410
+ * during propagation but auto-deliver after the outermost `rt.batch()`.
411
+ * - With `createRuntime({ effectStrategy: "eager" })`, invalidations flush
412
+ * automatically.
413
+ * - Reads performed inside cleanup do not become dependencies of the next run.
414
+ * - Disposing the returned scope prevents future re-runs.
415
+ *
416
+ * @see createRuntime
417
+ * @see computed
418
+ * @see memo
419
+ */
278
420
  declare function effect(fn: EffectFn): Destructor;
279
421
 
280
- type EffectStrategy = "flush" | "eager";
422
+ type EffectStrategy = "flush" | "eager" | "sab";
281
423
 
282
424
  interface RuntimeOptions {
425
+ /**
426
+ * Optional low-level runtime hooks forwarded to the execution context.
427
+ *
428
+ * These hooks are composed with Reflex's scheduler integration rather than
429
+ * replacing it.
430
+ */
283
431
  hooks?: EngineHooks;
432
+ /**
433
+ * Controls when invalidated effects are executed.
434
+ *
435
+ * - `"flush"` queues reruns until `rt.flush()` is called.
436
+ * - `"sab"` keeps lazy enqueue semantics but stabilizes effects after the
437
+ * outermost `rt.batch()` exits.
438
+ * - `"eager"` flushes reruns automatically.
439
+ *
440
+ * @default "flush"
441
+ */
284
442
  effectStrategy?: EffectStrategy;
285
443
  }
444
+ /**
445
+ * Push-based event stream that allows observers to subscribe to future values.
446
+ *
447
+ * `Event` is the read-only view of an event source. It does not expose
448
+ * mutation, only observation.
449
+ *
450
+ * @typeParam T - Event payload type.
451
+ */
286
452
  interface Event<T> {
453
+ /**
454
+ * Registers a callback for future event deliveries.
455
+ *
456
+ * @param fn - Callback invoked for each emitted value.
457
+ *
458
+ * @returns Destructor that unsubscribes `fn`.
459
+ */
287
460
  subscribe(fn: (value: T) => void): Destructor;
288
461
  }
462
+ /**
463
+ * Mutable event source created by `Runtime.event()`.
464
+ *
465
+ * @typeParam T - Event payload type.
466
+ */
289
467
  interface EventSource<T> extends Event<T> {
468
+ /**
469
+ * Emits a value to current subscribers using the runtime dispatcher.
470
+ *
471
+ * Nested emits are queued after the current delivery completes, preserving
472
+ * FIFO ordering across sources created by the same runtime.
473
+ *
474
+ * @param value - Event payload to deliver.
475
+ */
290
476
  emit(value: T): void;
291
477
  }
478
+ /**
479
+ * Connected Reflex runtime returned by `createRuntime()`.
480
+ *
481
+ * The runtime owns the event dispatcher, effect scheduler, and execution
482
+ * context used by the top-level Reflex primitives.
483
+ */
292
484
  interface Runtime {
485
+ batch<T>(fn: () => T): T;
486
+ /**
487
+ * Creates a new mutable event source associated with this runtime.
488
+ *
489
+ * @typeParam T - Event payload type.
490
+ *
491
+ * @returns Event source with `emit(value)` and `subscribe(fn)`.
492
+ */
293
493
  event<T>(): EventSource<T>;
494
+ /**
495
+ * Flushes queued effect re-runs immediately.
496
+ *
497
+ * In the default `"flush"` strategy, call this after writes when you want
498
+ * scheduled effects to observe the latest stable snapshot. In `"sab"` and
499
+ * `"eager"` it remains available as an explicit synchronization escape hatch.
500
+ */
294
501
  flush(): void;
502
+ /**
503
+ * Underlying execution context used by this runtime.
504
+ *
505
+ * Most application code does not need this. It exists for low-level
506
+ * integrations, tests, and diagnostics.
507
+ */
295
508
  readonly ctx: ExecutionContext;
296
509
  }
510
+ /**
511
+ * Creates and installs the active Reflex runtime.
512
+ *
513
+ * `createRuntime` wires together an execution context, effect scheduler, and
514
+ * event dispatcher, then makes that context the default runtime used by the
515
+ * top-level Reflex primitives exported from this package.
516
+ *
517
+ * @param options - Optional runtime configuration:
518
+ * - `effectStrategy` controls whether invalidated effects flush on
519
+ * `rt.flush()`, stabilize after the outermost batch, or run automatically.
520
+ * - `hooks` installs low-level runtime hooks that are composed with Reflex's
521
+ * scheduler integration.
522
+ *
523
+ * @returns Connected runtime with event creation, flushing, and context
524
+ * access.
525
+ *
526
+ * @example
527
+ * ```ts
528
+ * const rt = createRuntime();
529
+ * const ticks = rt.event<number>();
530
+ * const [count, setCount] = signal(0);
531
+ *
532
+ * ticks.subscribe((value) => {
533
+ * setCount((current) => current + value);
534
+ * });
535
+ *
536
+ * effect(() => {
537
+ * console.log(count());
538
+ * });
539
+ *
540
+ * ticks.emit(1);
541
+ * rt.flush();
542
+ * ```
543
+ *
544
+ * @remarks
545
+ * - Call this once during app startup or per test case to establish the active
546
+ * runtime.
547
+ * - Creating a new runtime replaces the previously active default context.
548
+ * - `rt.flush()` is primarily for scheduled effects; signals and computed
549
+ * reads stay current without it.
550
+ * - Event sources created by `rt.event()` share one dispatcher and preserve
551
+ * FIFO delivery order.
552
+ */
297
553
  declare function createRuntime(options?: RuntimeOptions): Runtime;
298
554
 
299
555
  type EventValue<E extends Event<unknown>> = E extends Event<infer T> ? T : never;
@@ -413,9 +669,54 @@ declare function scan<T, A>(source: Event<T>, seed: A, reducer: (acc: A, value:
413
669
  */
414
670
  declare function hold<T>(source: Event<T>, initial: T): [read: Accessor<T>, dispose: Destructor];
415
671
 
416
- interface SignalOptions {
417
- name: string;
418
- }
419
- declare function signal<T>(initialValue: T, options?: SignalOptions): readonly [value: Accessor<T>, setValue: Setter<T>];
672
+ /**
673
+ * Creates writable reactive state.
674
+ *
675
+ * `signal` returns a tuple containing a tracked read accessor and a setter.
676
+ * Reading the accessor inside `computed()`, `memo()`, or `effect()` registers
677
+ * a dependency. Writing through the setter updates the stored value
678
+ * synchronously and invalidates downstream reactive consumers only when the
679
+ * value actually changes.
680
+ *
681
+ * @typeParam T - Signal value type.
682
+ *
683
+ * @param initialValue - Initial signal value returned until a later write
684
+ * replaces it.
685
+ * @param options - Optional development diagnostics. `options.name` is used
686
+ * only in development builds when formatting setter error messages.
687
+ *
688
+ * @returns A readonly tuple:
689
+ * - `value` - tracked accessor that returns the current signal value.
690
+ * - `setValue` - setter that accepts either a direct value or an updater
691
+ * function receiving the previous value. The setter returns the committed
692
+ * next value.
693
+ *
694
+ * @example
695
+ * ```ts
696
+ * createRuntime();
697
+ *
698
+ * const [count, setCount] = signal(0);
699
+ *
700
+ * console.log(count()); // 0
701
+ *
702
+ * setCount(1);
703
+ * setCount((prev) => prev + 1);
704
+ *
705
+ * console.log(count()); // 2
706
+ * ```
707
+ *
708
+ * @remarks
709
+ * - Reads are synchronous and always return the latest committed value.
710
+ * - Same-value writes do not invalidate downstream computed values or effects.
711
+ * - Calling `setValue()` with no argument is only valid when `T` includes
712
+ * `undefined`.
713
+ * - In typical app code, call `createRuntime()` during setup before building
714
+ * the rest of the reactive graph.
715
+ *
716
+ * @see computed
717
+ * @see memo
718
+ * @see effect
719
+ */
720
+ declare function signal<T>(initialValue: T): readonly [Accessor<T>, Setter<T>];
420
721
 
421
- export { computed, createRuntime, effect, filter, hold, map, memo, merge, scan, signal, subscribeOnce };
722
+ export { computed, createRuntime, effect, filter, hold, map, memo, merge, scan, signal, subscribeOnce, withEffectCleanupRegistrar };
@@ -1,31 +1,226 @@
1
1
  /// <reference path="../globals.d.ts" />
2
2
 
3
+ /**
4
+ * Current lifecycle state of a resource request.
5
+ *
6
+ * - `"idle"` - no active request is in flight.
7
+ * - `"pending"` - the current request is still running.
8
+ * - `"resolved"` - the latest current request completed successfully.
9
+ * - `"rejected"` - the latest current request failed.
10
+ */
3
11
  type ResourceStatus = "idle" | "pending" | "resolved" | "rejected";
12
+ /**
13
+ * Guard object bound to a single resource request token.
14
+ *
15
+ * Loaders receive a guard so they can tell whether their work is still current
16
+ * before committing side effects or returning follow-up work.
17
+ */
4
18
  interface ResourceGuard {
19
+ /**
20
+ * Monotonic token identifying the request that produced this guard.
21
+ */
5
22
  readonly token: number;
23
+ /**
24
+ * Returns `true` while this request is still current and the resource has not
25
+ * been disposed.
26
+ */
6
27
  alive(): boolean;
7
28
  }
29
+ /**
30
+ * Mutable handle for manually settling a resource request.
31
+ *
32
+ * Manual resources expose this handle from `start()`. It extends
33
+ * `ResourceGuard` with methods that attempt to resolve or reject the current
34
+ * request.
35
+ *
36
+ * @typeParam T - Resolved value type.
37
+ * @typeParam E - Rejection type.
38
+ */
8
39
  interface ResourceHandle<T, E = unknown> extends ResourceGuard {
40
+ /**
41
+ * Resolves the request with `value`.
42
+ *
43
+ * @param value - Value to commit as the latest successful result.
44
+ *
45
+ * @returns `true` if the value was accepted, or `false` if this handle is
46
+ * stale or the resource has already been disposed.
47
+ */
9
48
  resolve(value: T): boolean;
49
+ /**
50
+ * Rejects the request with `error`.
51
+ *
52
+ * @param error - Error to commit as the latest failure.
53
+ *
54
+ * @returns `true` if the error was accepted, or `false` if this handle is
55
+ * stale or the resource has already been disposed.
56
+ */
10
57
  reject(error: E): boolean;
11
58
  }
59
+ /**
60
+ * Reactive view of a resource request lifecycle.
61
+ *
62
+ * A resource exposes tracked accessors for its status, latest value, latest
63
+ * error, and current request token, along with imperative controls to reset or
64
+ * dispose the resource.
65
+ *
66
+ * @typeParam T - Resolved value type.
67
+ * @typeParam E - Rejection type.
68
+ */
12
69
  interface Resource<T, E = unknown> {
70
+ /**
71
+ * Tracked accessor that returns the current request lifecycle state.
72
+ */
13
73
  readonly status: Accessor<ResourceStatus>;
74
+ /**
75
+ * Tracked accessor that returns the latest successfully resolved value, if
76
+ * one exists.
77
+ *
78
+ * The last resolved value is retained while a newer request is pending or
79
+ * rejected.
80
+ */
14
81
  readonly value: Accessor<T | undefined>;
82
+ /**
83
+ * Tracked accessor that returns the latest rejected error, if one exists.
84
+ */
15
85
  readonly error: Accessor<E | undefined>;
86
+ /**
87
+ * Tracked accessor that returns the current monotonic request token.
88
+ *
89
+ * The token increments whenever a new request starts, the resource is
90
+ * cleared, or the resource is disposed.
91
+ */
16
92
  readonly token: Accessor<number>;
93
+ /**
94
+ * Resets the resource to the `"idle"` state and invalidates any in-flight
95
+ * request.
96
+ */
17
97
  clear(): void;
98
+ /**
99
+ * Disposes the resource permanently and invalidates any in-flight request.
100
+ *
101
+ * After disposal, the resource stops reacting to future source changes or
102
+ * refetch requests.
103
+ */
18
104
  dispose(): void;
19
105
  }
106
+ /**
107
+ * Imperative resource whose requests are started and settled manually.
108
+ *
109
+ * @typeParam T - Resolved value type.
110
+ * @typeParam E - Rejection type.
111
+ */
20
112
  interface ManualResource<T, E = unknown> extends Resource<T, E> {
113
+ /**
114
+ * Starts a new request and returns a handle that can resolve or reject it.
115
+ *
116
+ * Starting a new request invalidates all older handles.
117
+ */
21
118
  start(): ResourceHandle<T, E>;
22
119
  }
120
+ /**
121
+ * Auto-loading resource backed by a loader function.
122
+ *
123
+ * @typeParam T - Resolved value type.
124
+ * @typeParam E - Rejection type.
125
+ */
23
126
  interface AsyncResource<T, E = unknown> extends Resource<T, E> {
127
+ /**
128
+ * Requests another load.
129
+ *
130
+ * In the default runtime strategy, the reload starts when the surrounding
131
+ * runtime flushes scheduled effects.
132
+ */
24
133
  refetch(): void;
25
134
  }
135
+ /**
136
+ * Loader used by `resource(load)`.
137
+ *
138
+ * It receives a guard for the current request and may return either a value or
139
+ * a promise-like value.
140
+ *
141
+ * @typeParam T - Resolved value type.
142
+ */
26
143
  type ResourceJob<T> = (guard: ResourceGuard) => T | PromiseLike<T>;
144
+ /**
145
+ * Loader used by `resource(source, load)`.
146
+ *
147
+ * It receives the latest source value plus a guard for the current request, and
148
+ * may return either a value or a promise-like value.
149
+ *
150
+ * @typeParam S - Source value type.
151
+ * @typeParam T - Resolved value type.
152
+ */
27
153
  type ResourceLoader<S, T> = (source: S, guard: ResourceGuard) => T | PromiseLike<T>;
154
+ /**
155
+ * Returns `true` when the resource's current request is pending.
156
+ *
157
+ * @param resource - Resource to inspect.
158
+ *
159
+ * @returns Whether `resource.status()` currently equals `"pending"`.
160
+ */
28
161
  declare function isPending(resource: Resource<unknown, unknown>): boolean;
162
+ /**
163
+ * Creates an unstable resource for manual or loader-driven async state.
164
+ *
165
+ * `resource` models request lifecycles with tracked accessors for `status`,
166
+ * `value`, `error`, and `token`. It can be used in three modes:
167
+ *
168
+ * - `resource<T, E>()` creates a manual resource. Call `start()` to begin a
169
+ * request, then settle that request through the returned handle.
170
+ * - `resource(load)` creates an auto-loading resource that starts immediately
171
+ * and can be reloaded with `refetch()`.
172
+ * - `resource(source, load)` creates a source-driven resource that reloads
173
+ * whenever `source()` changes.
174
+ *
175
+ * @typeParam S - Source value type for the source-driven overload.
176
+ * @typeParam T - Resolved value type.
177
+ * @typeParam E - Rejection type tracked by `error()`.
178
+ *
179
+ * @param sourceOrLoad - Either the reactive source accessor to watch or the
180
+ * no-source loader function, depending on the overload.
181
+ * @param maybeLoad - Loader used with the source-driven overload.
182
+ *
183
+ * @returns Either a `ManualResource` or an `AsyncResource`, depending on the
184
+ * selected overload.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * import { createRuntime, signal } from "@volynets/reflex";
189
+ * import { resource } from "@volynets/reflex/unstable";
190
+ *
191
+ * const rt = createRuntime();
192
+ * const [userId, setUserId] = signal(1);
193
+ *
194
+ * const user = resource(() => userId(), async (id) => {
195
+ * await Promise.resolve();
196
+ * return { id, name: `user-${id}` };
197
+ * });
198
+ *
199
+ * console.log(user.status()); // "pending"
200
+ *
201
+ * setUserId(2);
202
+ * rt.flush();
203
+ * ```
204
+ *
205
+ * @remarks
206
+ * - This API is exported from `@volynets/reflex/unstable` and may change
207
+ * between releases.
208
+ * - Each new request increments `token()`. Older handles and stale async
209
+ * resolutions are ignored automatically.
210
+ * - `value()` retains the last resolved value while a newer request is pending
211
+ * or rejected.
212
+ * - `clear()` resets the resource to `"idle"` and invalidates the current
213
+ * request.
214
+ * - `dispose()` invalidates the current request and permanently stops future
215
+ * updates.
216
+ * - `refetch()` and source changes schedule a new load through the runtime.
217
+ * With the default effect strategy, call `rt.flush()` to start it.
218
+ * - If a source accessor or loader throws synchronously, the current request
219
+ * is rejected with that error.
220
+ *
221
+ * @see isPending
222
+ * @see createRuntime
223
+ */
29
224
  declare function resource<T, E = unknown>(): ManualResource<T, E>;
30
225
  declare function resource<T, E = unknown>(load: ResourceJob<T>): AsyncResource<T, E>;
31
226
  declare function resource<S, T, E = unknown>(source: Accessor<S>, load: ResourceLoader<S, T>): AsyncResource<T, E>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volynets/reflex",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Public Reflex facade with a connected runtime",
6
6
  "license": "MIT",
@@ -10,17 +10,26 @@
10
10
  "sideEffects": false,
11
11
  "exports": {
12
12
  ".": {
13
+ "source": {
14
+ "types": "./src/index.ts",
15
+ "default": "./src/index.ts"
16
+ },
13
17
  "types": "./dist/globals.d.ts",
14
18
  "import": "./dist/esm/index.js",
15
19
  "require": "./dist/cjs/index.cjs"
16
20
  },
17
21
  "./unstable": {
22
+ "source": {
23
+ "types": "./src/unstable/index.ts",
24
+ "default": "./src/unstable/index.ts"
25
+ },
18
26
  "types": "./dist/unstable/index.d.ts",
19
27
  "import": "./dist/esm/unstable/index.js",
20
28
  "require": "./dist/cjs/unstable/index.cjs"
21
29
  }
22
30
  },
23
31
  "files": [
32
+ "src",
24
33
  "dist/cjs",
25
34
  "dist/esm",
26
35
  "dist/globals.d.ts",