uneventful 0.0.13 → 0.0.15

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.
@@ -1,5 +1,5 @@
1
- import { s as start, j as connect, p as isValue, h as isError, f as markHandled } from './jobutils-O9tOTNKC.mjs';
2
- import { i as isFunction } from './utils-BgqyDPjA.mjs';
1
+ import { s as start, j as connect, p as isValue, h as isError, f as markHandled } from './jobutils-DYe7dvZO.mjs';
2
+ import { i as isFunction } from './utils-CwhAPj4M.mjs';
3
3
 
4
4
  function callOrWait(source, method, handler, noArgs) {
5
5
  if (source && isFunction(source[method]))
package/dist/ext.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as PlainFunction } from './types-pElgImr7.js';
1
+ import { P as PlainFunction } from './types-C3WWddF2.js';
2
2
 
3
3
  /**
4
4
  * This module provides helpers for creating *extensions*: a way of extending
package/dist/ext.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { s as setMap } from './utils-BgqyDPjA.mjs';
1
+ import { s as setMap } from './utils-CwhAPj4M.mjs';
2
2
 
3
3
  function ext(factory, map = /* @__PURE__ */ new WeakMap()) {
4
4
  return (tgt) => map.has(tgt) ? map.get(tgt) : setMap(map, tgt, factory(tgt, map));
@@ -1,5 +1,5 @@
1
- import { M as pushCtx, N as popCtx, c as backpressure, I as IsStream, _ as _Job, d as root, h as isError, f as markHandled, g as getJob, o as currentCell } from './jobutils-O9tOTNKC.mjs';
2
- import { h as batch, d as defer, c as apply, s as setMap } from './utils-BgqyDPjA.mjs';
1
+ import { N as pushCtx, O as popCtx, c as backpressure, I as IsStream, l as currentCell, _ as _Job, d as root, h as isError, f as markHandled, g as getJob } from './jobutils-DYe7dvZO.mjs';
2
+ import { d as defer, b as apply, s as setMap, h as batch } from './utils-CwhAPj4M.mjs';
3
3
 
4
4
  const ruleQueues = /* @__PURE__ */ new WeakMap();
5
5
  function ruleQueue(scheduleFn = defer) {
@@ -209,6 +209,8 @@ class Cell {
209
209
  this.catchUp();
210
210
  if (this.adding === s)
211
211
  s.ts = this.lastChanged;
212
+ } else if (dep === false) {
213
+ throw new Error("Reactive values can't be used while creating singletons or lazy constants");
212
214
  } else
213
215
  this.catchUp();
214
216
  if (this.flags & 16 /* Error */)
@@ -241,7 +243,7 @@ class Cell {
241
243
  if (this.shouldWrite(val !== this.value || isErr !== !!(this.flags & 16 /* Error */))) {
242
244
  this.value = val;
243
245
  this.lastChanged = timestamp;
244
- this.flags = isErr ? this.flags | 16 /* Error */ : this.flags & ~16 /* Error */;
246
+ this.flags = isErr ? this.flags | 16 /* Error */ : this.flags & -17 /* Error */;
245
247
  }
246
248
  this.compute = isErr ? throwValue : returnValue;
247
249
  }
@@ -318,11 +320,11 @@ class Cell {
318
320
  throw e;
319
321
  }
320
322
  } finally {
321
- this.flags &= ~32 /* Running */;
323
+ this.flags &= -33 /* Running */;
322
324
  if (this.flags & 64 /* Stateful */) {
323
325
  demandChanges.delete(this);
324
326
  if (this.flags & (16 /* Error */ | 2 /* Stopped */)) {
325
- this.flags &= ~2 /* Stopped */;
327
+ this.flags &= -3 /* Stopped */;
326
328
  this.job?.restart();
327
329
  } else if (this.flags & 1 /* Observed */) ; else {
328
330
  this.job?.restart();
@@ -350,7 +352,7 @@ class Cell {
350
352
  mksub(dummySource ||= Cell.mkValue(null), this);
351
353
  return;
352
354
  }
353
- this.flags &= ~4 /* Compute */;
355
+ this.flags &= -5 /* Compute */;
354
356
  if (this.flags & 132 /* Variable */)
355
357
  return;
356
358
  while (this.adding)
@@ -480,7 +482,7 @@ class Cell {
480
482
  try {
481
483
  return apply(fn, thisArg, args);
482
484
  } finally {
483
- this.flags &= ~8 /* Peeking */;
485
+ this.flags &= -9 /* Peeking */;
484
486
  }
485
487
  }
486
488
  unchangedIf(newVal, equals) {
@@ -572,7 +574,13 @@ function perSignal(func, site, name) {
572
574
  if (findOrCreateMemos(hooks2, site, 1)) {
573
575
  return getMemo(hooks2, 1)();
574
576
  } else {
575
- const result = func(...args);
577
+ let result;
578
+ pushCtx(void 0, false);
579
+ try {
580
+ result = func(...args);
581
+ } finally {
582
+ popCtx();
583
+ }
576
584
  setMemo(hooks2, 1, () => result);
577
585
  return result;
578
586
  }
@@ -1,4 +1,4 @@
1
- import { h as batch, d as defer, i as isFunction, G as GeneratorBase, c as apply } from './utils-BgqyDPjA.mjs';
1
+ import { h as batch, d as defer, i as isFunction, G as GeneratorBase, b as apply } from './utils-CwhAPj4M.mjs';
2
2
 
3
3
  function resolve(request, val) {
4
4
  request("next", val);
@@ -80,7 +80,7 @@ function popCtx() {
80
80
  currentCell = cells.pop();
81
81
  }
82
82
  function cellJob() {
83
- return currentJob ||= currentCell?.getJob();
83
+ return currentJob ||= currentCell ? currentCell.getJob() : void 0;
84
84
  }
85
85
 
86
86
  const catchers = /* @__PURE__ */ new WeakMap(), defaultCatch = (e) => {
@@ -93,6 +93,20 @@ const pulls = /* @__PURE__ */ batch((pulls2) => {
93
93
  conn.doPull();
94
94
  }
95
95
  }, defer);
96
+ let singletons;
97
+ function resetSingletons() {
98
+ singletons = /* @__PURE__ */ new WeakMap();
99
+ activeServices.forEach((r) => {
100
+ const c = r.deref();
101
+ if (c) {
102
+ c.valid = false;
103
+ c.value = void 0;
104
+ } else {
105
+ activeServices.delete(r);
106
+ }
107
+ });
108
+ }
109
+ const activeServices = /* @__PURE__ */ new Set();
96
110
 
97
111
  function chain() {
98
112
  return link(0, void 0, void 0);
@@ -259,7 +273,7 @@ class _Job {
259
273
  });
260
274
  }
261
275
  result() {
262
- return this._done || currentCell?.recalcWhen(this, recalcJob) || void 0;
276
+ return this._done || currentCell && currentCell.recalcWhen(this, recalcJob) || void 0;
263
277
  }
264
278
  get [Symbol.toStringTag]() {
265
279
  return "Job";
@@ -451,6 +465,7 @@ let root;
451
465
  newRoot();
452
466
  function newRoot() {
453
467
  root?.end();
468
+ resetSingletons();
454
469
  const job = root = new _Job().asyncCatch(defaultCatch);
455
470
  job.release(() => root === job && (root = null));
456
471
  return root;
@@ -614,7 +629,7 @@ function start(init, fn) {
614
629
  return getJob().start(init, fn);
615
630
  }
616
631
  function isJobActive() {
617
- return !!(currentJob || currentCell?.isObserved());
632
+ return !!(currentJob || currentCell && currentCell.isObserved());
618
633
  }
619
634
  const timers = /* @__PURE__ */ new WeakMap();
620
635
  function timeout(ms = 0, job = getJob()) {
@@ -679,4 +694,4 @@ function task(fn, _ctx, desc) {
679
694
  };
680
695
  }
681
696
 
682
- export { CancelError as A, pipe as B, CancelResult as C, compose as D, ErrorResult as E, into as F, isJobActive as G, timeout as H, IsStream as I, abortSignal as J, task as K, currentJob as L, pushCtx as M, popCtx as N, ValueResult as V, _Job as _, rejecter as a, resolve as b, backpressure as c, root as d, isUnhandled as e, markHandled as f, getJob as g, isError as h, isCancel as i, connect as j, fulfillPromise as k, restarting as l, must as m, noop as n, currentCell as o, isValue as p, nativePromise as q, resolver as r, start as s, throttle as t, makeJob as u, newRoot as v, reject as w, isHandled as x, getResult as y, propagateResult as z };
697
+ export { makeJob as A, nativePromise as B, CancelError as C, newRoot as D, ErrorResult as E, pipe as F, propagateResult as G, reject as H, IsStream as I, task as J, timeout as K, singletons as L, activeServices as M, pushCtx as N, popCtx as O, currentJob as P, ValueResult as V, _Job as _, resolver as a, rejecter as b, backpressure as c, root as d, isUnhandled as e, markHandled as f, getJob as g, isError as h, isCancel as i, connect as j, fulfillPromise as k, currentCell as l, must as m, noop as n, restarting as o, isValue as p, CancelResult as q, resolve as r, start as s, throttle as t, abortSignal as u, compose as v, getResult as w, into as x, isHandled as y, isJobActive as z };
package/dist/mod.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { J as Job, C as CleanupFn, Y as Yielding, S as Source, D as DisposeFn, a as Stream, B as Backpressure, T as Transformer, b as Sink, O as OptionalCleanup, c as StartFn, d as StartObj, A as AnyFunction } from './types-pElgImr7.js';
2
- export { e as AsyncStart, z as CancelError, m as CancelResult, K as Connection, E as ErrorResult, H as HandledError, I as Inlet, M as IsStream, g as JobIterator, o as JobResult, N as Nothing, P as PlainFunction, i as RecalcSource, R as Request, L as SignalSource, h as Suspend, f as SyncStart, G as Throttle, U as UnhandledError, V as ValueResult, F as backpressure, Z as compose, Q as connect, x as fulfillPromise, w as getResult, _ as into, p as isCancel, s as isError, u as isHandled, t as isUnhandled, q as isValue, v as markHandled, n as noop, X as pipe, y as propagateResult, j as reject, l as rejecter, r as resolve, k as resolver, W as throttle } from './types-pElgImr7.js';
3
- export { a as Each, E as EachResult, N as NextMethod, U as UntilMethod, e as each, f as forEach, i as isObserved, n as next, r as recalcWhen } from './sinks-B_LfTZgF.js';
1
+ import { J as Job, C as CleanupFn, Y as Yielding, S as Source, B as Backpressure, D as DisposeFn, a as Stream, T as Transformer, b as Sink, O as OptionalCleanup, A as AnyFunction, c as StartFn, d as StartObj } from './types-C3WWddF2.js';
2
+ export { e as AsyncStart, f as CancelError, g as CancelResult, h as Connection, E as ErrorResult, H as HandledError, I as Inlet, i as IsStream, j as JobIterator, k as JobResult, N as Nothing, P as PlainFunction, R as RecalcSource, l as Request, m as SignalSource, n as Suspend, o as SyncStart, p as Throttle, U as UnhandledError, V as ValueResult, q as backpressure, r as compose, s as connect, t as fulfillPromise, u as getResult, v as into, w as isCancel, x as isError, y as isHandled, z as isUnhandled, F as isValue, G as markHandled, K as noop, L as pipe, M as propagateResult, Q as reject, W as rejecter, X as resolve, Z as resolver, _ as throttle } from './types-C3WWddF2.js';
3
+ export { E as Each, a as EachResult, N as NextMethod, U as UntilMethod, e as each, f as forEach, i as isObserved, n as next, r as recalcWhen } from './sinks-CoXxbJrs.js';
4
4
 
5
5
  /**
6
6
  * Invoke a no-argument function as a microtask, using queueMicrotask or Promise.resolve().then()
package/dist/mod.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { d as defer, i as isFunction } from './utils-BgqyDPjA.mjs';
2
- import { r as resolver, a as rejecter, b as resolve, I as IsStream, c as backpressure, m as must, s as start, g as getJob, n as noop, d as root, i as isCancel, e as isUnhandled, f as markHandled, h as isError, t as throttle, j as connect, k as fulfillPromise, l as restarting, o as currentCell, p as isValue } from './jobutils-O9tOTNKC.mjs';
3
- export { A as CancelError, C as CancelResult, E as ErrorResult, V as ValueResult, J as abortSignal, D as compose, y as getResult, F as into, x as isHandled, G as isJobActive, u as makeJob, q as nativePromise, v as newRoot, B as pipe, z as propagateResult, w as reject, K as task, H as timeout } from './jobutils-O9tOTNKC.mjs';
4
- import { c as callOrWait, m as mustBeSourceOrSignal } from './call-or-wait-CvOF0pao.mjs';
1
+ import { d as defer, i as isFunction } from './utils-CwhAPj4M.mjs';
2
+ import { r as resolve, a as resolver, b as rejecter, I as IsStream, c as backpressure, m as must, s as start, g as getJob, n as noop, d as root, i as isCancel, e as isUnhandled, f as markHandled, h as isError, t as throttle, j as connect, k as fulfillPromise, l as currentCell, o as restarting, p as isValue } from './jobutils-DYe7dvZO.mjs';
3
+ export { C as CancelError, q as CancelResult, E as ErrorResult, V as ValueResult, u as abortSignal, v as compose, w as getResult, x as into, y as isHandled, z as isJobActive, A as makeJob, B as nativePromise, D as newRoot, F as pipe, G as propagateResult, H as reject, J as task, K as timeout } from './jobutils-DYe7dvZO.mjs';
4
+ import { c as callOrWait, m as mustBeSourceOrSignal } from './call-or-wait-BTeEcToS.mjs';
5
5
 
6
6
  function* to(p) {
7
7
  return yield (res) => Promise.resolve(p).then(resolver(res), rejecter(res));
@@ -276,10 +276,10 @@ function forEach(src, sink, inlet) {
276
276
  return (src2) => forEach(src2, sink, inlet);
277
277
  }
278
278
  function recalcWhen(fnOrKey, fn) {
279
- currentCell?.recalcWhen(fnOrKey, fn);
279
+ currentCell && currentCell.recalcWhen(fnOrKey, fn);
280
280
  }
281
281
  function isObserved() {
282
- return currentCell?.isObserved();
282
+ return currentCell ? currentCell.isObserved() : void 0;
283
283
  }
284
284
 
285
285
  function concat(sources) {
package/dist/shared.d.ts CHANGED
@@ -1,33 +1,84 @@
1
- import { Y as Yielding } from './types-pElgImr7.js';
1
+ import { Y as Yielding } from './types-C3WWddF2.js';
2
2
 
3
3
  /** @inline */
4
4
  type CallSite = TemplateStringsArray;
5
5
 
6
6
  /**
7
7
  * Tools for sharing tasks, values, services, etc., especially across job
8
- * boundaries.
8
+ * boundaries, but also across reactive expression lifetimes (similar to React
9
+ * hooks).
9
10
  *
10
11
  * @module uneventful/shared
11
12
  * @disableGroups
12
13
  */
13
14
 
14
15
  /**
15
- * Wrap a factory function to create a singleton service accessor
16
+ * A function, or class constructor producing a value that will be cached by
17
+ * `$()` and `service()` accessors for the life of the root job, or until
18
+ * overridden or unset via `$cache`.
19
+ *
20
+ * The function or constructor will be run in the then-current root job, and its
21
+ * value cached. If the function is a native generator function, it is
22
+ * automatically wrapped with {@link fork}(), so that its job will not end when
23
+ * the generator ends, and the result can be waited on by multiple callers. (If
24
+ * your factory is *not* a native generator function but still returns a
25
+ * generator to produce an async result, you should wrap that generator with
26
+ * {@link fork} before returning it.)
27
+ *
28
+ * Note: if an original or replacement factory (set via
29
+ * {@link $cache.replace}()) directly or indirectly requests its own value (even
30
+ * via a {@link service}() accessor), an error is thrown to prevent infinite
31
+ * recursion. An error is also thrown if a reactive value is directly or
32
+ * indirectly accessed during the factory's execution, to prevent your cached
33
+ * singleton from unintentionally retaining a stale value. (Rules and
34
+ * {@link uneventful/signals.fx fx()} are unaffected, as they will update their
35
+ * state as needed.)
36
+ *
37
+ * @category Types and Interfaces
38
+ */
39
+ type SingletonFactory<T> = (() => T) | (new () => T);
40
+ /**
41
+ * A function, or class constructor producing a value that will be cached by
42
+ * ```$``()``` as a permanent-per-signal constant at a given call site.
16
43
  *
17
- * The returned function, when called, will run the factory in a new job and
18
- * cache the result. The job the factory ran in will end when the {@link root}
19
- * job does, after which the cached result will be cleared.
44
+ * The function or constructor will be run *without* an active job, and its
45
+ * value cached. While it runs, any attempt to directly access a reactive value
46
+ * or expression will be blocked by a thrown error, to prevent your cached
47
+ * result from capturing a stale value.
20
48
  *
21
- * @param factory A function returning whatever result you want to share: a
22
- * value, a function, an object, etc. It will be called at most once per root
23
- * job lifetime, in a job that is an immediate child of the root job.
49
+ * Note that unlike {@link SingletonFactory}, a ConstantFactory *cannot* use job
50
+ * APIs, rules, or fx(), nor is there any special handling for generator
51
+ * functions. None of these things make any sense for a lazy constant, as there
52
+ * is no way to clean up after them: they simply exist for the life of the
53
+ * signal and so should be relatively stateless.
24
54
  *
25
- * (Note: if your factory is a native generator function, it is automatically
26
- * wrapped with {@link fork}() so that its job will not end when the generator
27
- * ends, and the result can be waited on by multiple callers. If your factory
28
- * is *not* a native generator function but still returns a generator to produce
29
- * an async result, you should wrap that generator with {@link fork} before
30
- * returning it.)
55
+ * @category Types and Interfaces
56
+ */
57
+ type ConstantFactory<T> = (() => T) | (new () => T);
58
+ /**
59
+ * Wrap a factory function to create a "service" accessor (a shorthand function
60
+ * for getting {@link $ `$(factory)`}).
61
+ *
62
+ * This function returns an equivalent to `() => $(factory)` or `$.bind(null,
63
+ * factory)`, but optimized for raw access speed (close to a module export
64
+ * binding) at the expense of slightly-higher memory use.
65
+ *
66
+ * Every distinct `factory` passed to `service()` consumes some bookkeeping
67
+ * space that will *not* be released until the application exits. (Or the next
68
+ * call to {@link newRoot}() happens, but that's normally only used for
69
+ * testing!)
70
+ *
71
+ * So this is for stable/static accessors: module-level constants, classes,
72
+ * etc., not factories that are created on the fly from closures in functions
73
+ * that get called a lot. (For those use cases, just substitute `() =>
74
+ * $(factory)` or `$.bind(null, factory)`.)
75
+ *
76
+ * @param factory The {@link SingletonFactory} function or class whose
77
+ * shared/cached value the accessor returns. See {@link SingletonFactory} for
78
+ * important details on how the factory is run and the restrictions on what it
79
+ * can do. Note that since the accessor is just shorthand for `$()`, its state
80
+ * and implementation can be created, deleted, replaced, etc. using the
81
+ * {@link $cache} APIs.
31
82
  *
32
83
  * @remarks Note that if you want your code to be testable with {@link newRoot},
33
84
  * you should avoid storing the *result* of calling the service accessor
@@ -41,9 +92,9 @@ type CallSite = TemplateStringsArray;
41
92
  * such a thing should not be necessary in your production builds, however,
42
93
  * since at runtime you will normally only ever have one root job.)
43
94
  *
44
- * @category Resources
95
+ * @category Singletons & Lazy Constants
45
96
  */
46
- declare function service<T>(factory: () => T): () => T;
97
+ declare function service<T>(factory: SingletonFactory<T>): () => T;
47
98
  /**
48
99
  * Proxy an object so it "expires" (becomes inaccessible) with the calling job.
49
100
  *
@@ -65,31 +116,30 @@ declare function fork<T, F extends (...args: any[]) => Yielding<T>>(genFunc: F,
65
116
  declare function fork<T, F extends (...args: any[]) => Yielding<T>, D extends {
66
117
  value?: F;
67
118
  }>(clsOrProto: any, name: string | symbol, desc: D): D;
68
- /** @inline */
69
- type Factory<T> = (() => T) | (new () => T);
70
119
  /**
71
120
  * Return a singleton instance for the given factory
72
121
  *
73
- * Every call to `$()` with a given factory will return the same result. (Unless
74
- * overridden using {@link $cache.set}, {@link $cache.unset} or
75
- * {@link $cache.replace}.) On first use, the factory is called (or
76
- * constructed, if it's a class) and the result (if not an error) is cached for
77
- * future calls.
122
+ * Every call to `$()` with a given factory will return the same result. (Until
123
+ * a new root job is started, or the result is overridden using
124
+ * {@link $cache.set}, {@link $cache.unset} or {@link $cache.replace}.) On
125
+ * first use, the factory is called (or constructed, if it's a class) and the
126
+ * result (if not an error) is cached for future calls.
78
127
  *
128
+ * @param factory The {@link SingletonFactory} function or class whose
129
+ * shared/cached value you want to get. (See {@link SingletonFactory} for
130
+ * important details on how the factory is run and the restrictions on what it
131
+ * can do.)
79
132
  */
80
- declare function $<T>(factory: Factory<T>): T;
133
+ declare function $<T>(factory: SingletonFactory<T>): T;
81
134
  /**
82
135
  * Create a per-signal lazy constant, via ```$``()```
83
136
  *
84
137
  * When you call ```$``(factory)``` inside a given signal function for the first
85
- * time, `factory()` will be called (or constructed, if it's a class) and
86
- * returned, and the result cached for future calls *at the same location in
87
- * that specific signal*. An error results if called outside a signal function.
88
- *
89
- * The primary difference between this and the singleton operator (plain `$()`),
90
- * is that lazy constants are singletons *per call-site*, *per signal*. A
91
- * specific invocation of ```$``()``` in a specific signal will always return
92
- * the same value.
138
+ * time, the {@link ConstantFactory} will be called (or constructed, if it's a
139
+ * class) and the returned result will be cached for ALL future calls *at the
140
+ * same code location in that specific signal*. An error results if called
141
+ * outside a signal function. See the {@link ConstantFactory} docs for more
142
+ * details on how the factory is called, and the restrictions on what it can do.
93
143
  *
94
144
  * @remarks
95
145
  * Lazy constants are somewhat similar in concept to a React `useMemo()`, but
@@ -107,8 +157,12 @@ declare function $<T>(factory: Factory<T>): T;
107
157
  * call a wrapping function more than once in a signal, and expect to get
108
158
  * different results: a lazy constant is a per-signal *constant*, not a React
109
159
  * hook!)
160
+ *
161
+ * @experimental
110
162
  */
111
- declare function $(callSite: CallSite): <T>(factory: Factory<T>) => T;
163
+ declare function $(callSite: CallSite): <T>(factory: ConstantFactory<T>) => T;
164
+ /** @hidden - this makes this the generic for bind() */
165
+ declare function $<T>(factory: SingletonFactory<T>): T;
112
166
  /**
113
167
  * Utilities for manipulating the singleton cache (e.g. for testing)
114
168
  *
@@ -122,26 +176,45 @@ declare const $cache: {
122
176
  * testing)
123
177
  *
124
178
  * All subsequent calls to `$(factory)` will return the given result, until
125
- * manually set again, or reset via {@link $cache.unset}.
179
+ * manually set again, reset via {@link $cache.unset}, or cleared by
180
+ * {@link newRoot}().
126
181
  */
127
- set<T>(factory: Factory<T>, result: T): void;
182
+ set<T>(factory: SingletonFactory<T>, result: T): void;
128
183
  /**
129
184
  * Unset the cached singleton for a given factory, such that the next call
130
185
  * to `$(factory)` will create a new instance.
131
186
  */
132
- unset<T>(factory: Factory<T>): void;
187
+ unset<T>(factory: SingletonFactory<T>): void;
133
188
  /**
134
- * Replace the implementation for a given factory, such that future calls to
135
- * `$(factory)` will call or construct the replacement instead.
189
+ * Replace the implementation for a given factory (the "original"), such
190
+ * that future calls to `$(original)` will call or construct the replacement
191
+ * instead.
192
+ *
193
+ * Replacements are run under the same conditions as any other
194
+ * {@link SingletonFactory}: root job, no reactive reads, and generator
195
+ * function auto-{@link fork}()ing. They don't have to be *implemented* the
196
+ * same way, though: the original could be a class, and the replacement a
197
+ * function, or vice versa: they just need to produce something that
198
+ * satisfies the type expected by the original's consumers.
136
199
  *
137
- * If the replacement is omitted, null, or undefined, future calls will
138
- * invoke the original factory again.
200
+ * The replacement can even be a generic wrapper, as the current creation
201
+ * target (i.e. the "original" factory) can be obtained via
202
+ * {@link $cache.creating}(). If the replacement is omitted, null, or
203
+ * undefined, future calls will invoke the original factory again.
139
204
  *
140
205
  * (Note: in all cases the replacement will not take effect if there's
141
- * already a cached singleton, so you may wish to call
206
+ * already a cached singleton, so you may wish to also call
142
207
  * {@link $cache.unset}() to ensure a future call is actually executed.)
143
208
  */
144
- replace<T>(factory: Factory<T>, replacement?: Factory<T>): void;
209
+ replace<T>(original: SingletonFactory<T>, replacement?: SingletonFactory<T>): void;
210
+ /**
211
+ * The original factory whose singleton `$()` is currently creating, if any.
212
+ * (Only useful for generic {@link replace} factories.)
213
+ *
214
+ * Note: The return value is the original factory argument that was passed
215
+ * to `$()`, not its replacement.
216
+ */
217
+ creating(): SingletonFactory<unknown>;
145
218
  };
146
219
 
147
- export { $, $cache, expiring, fork, service };
220
+ export { $, $cache, type ConstantFactory, type SingletonFactory, expiring, fork, service };
package/dist/shared.mjs CHANGED
@@ -1,25 +1,20 @@
1
- import { p as perSignal } from './hooks-BnMYHRlM.mjs';
2
- import { d as root, m as must, s as start, n as noop } from './jobutils-O9tOTNKC.mjs';
3
- import { a as isGeneratorFunction, b as decorateMethod, i as isFunction, c as apply, s as setMap, e as isClass } from './utils-BgqyDPjA.mjs';
1
+ import { L as singletons, m as must, s as start, n as noop, M as activeServices, N as pushCtx, d as root, O as popCtx } from './jobutils-DYe7dvZO.mjs';
2
+ import { p as perSignal } from './hooks-Bx4Lqd9S.mjs';
3
+ import { i as isFunction, s as setMap, a as decorateMethod, b as apply, c as isGeneratorFunction, e as isClass } from './utils-CwhAPj4M.mjs';
4
4
 
5
5
  function service(factory) {
6
- let known = false, value = void 0;
7
- if (isGeneratorFunction(factory))
8
- factory = fork(factory);
9
- return () => {
10
- if (known)
11
- return value;
12
- root.start(() => {
13
- value = factory();
14
- known = true;
15
- must(() => {
16
- value = void 0;
17
- known = false;
18
- });
19
- });
20
- return value;
21
- };
6
+ if (services.has(factory))
7
+ return services.get(factory).get;
8
+ const c = {
9
+ valid: false,
10
+ value: noop,
11
+ // prebuild shape for object/function pointer
12
+ get: () => c.valid ? c.value : (c.value = $(factory), c.valid = true, c.value)
13
+ }, r = new WeakRef(c);
14
+ activeServices.add(r);
15
+ return setMap(services, factory, setMap(services, c.get, c)).get;
22
16
  }
17
+ const services = /* @__PURE__ */ new WeakMap();
23
18
  function expiring(obj) {
24
19
  const p = Proxy.revocable(obj, {});
25
20
  must(p.revoke);
@@ -49,17 +44,32 @@ function fork(genOrFunc, ...args) {
49
44
  }
50
45
  }
51
46
  const forks = /* @__PURE__ */ new WeakMap();
52
- const constants = /* @__PURE__ */ new WeakMap(), factories = /* @__PURE__ */ new WeakMap();
47
+ const factories = /* @__PURE__ */ new WeakMap();
53
48
  function $(key) {
54
49
  if (isFunction(key)) {
55
- return constants.has(key) ? constants.get(key) : setMap(
56
- constants,
50
+ return singletons.has(key) ? singletons.get(key) : setMap(
51
+ singletons,
57
52
  key,
58
- callOrConstruct(factories.has(key) ? factories.get(key) : key)
53
+ createSingleton(key, factories.has(key) ? factories.get(key) : key)
59
54
  );
60
55
  }
61
56
  return perSignal(callOrConstruct, key, "$``() ");
62
57
  }
58
+ const stack = [];
59
+ function createSingleton(key, f) {
60
+ if (stack.indexOf(key) > -1)
61
+ throw new Error("Singleton factory depends on itself");
62
+ if (isGeneratorFunction(f))
63
+ f = fork(f);
64
+ stack.unshift(key);
65
+ pushCtx(root, false);
66
+ try {
67
+ return callOrConstruct(f);
68
+ } finally {
69
+ stack.shift();
70
+ popCtx();
71
+ }
72
+ }
63
73
  function callOrConstruct(f) {
64
74
  return isClass(f) ? new f() : f();
65
75
  }
@@ -69,31 +79,62 @@ const $cache = {
69
79
  * testing)
70
80
  *
71
81
  * All subsequent calls to `$(factory)` will return the given result, until
72
- * manually set again, or reset via {@link $cache.unset}.
82
+ * manually set again, reset via {@link $cache.unset}, or cleared by
83
+ * {@link newRoot}().
73
84
  */
74
85
  set(factory, result) {
75
- constants.set(factory, result);
86
+ singletons.set(factory, result);
87
+ const a = services.get(factory);
88
+ if (a) {
89
+ a.valid = true;
90
+ a.value = result;
91
+ }
76
92
  },
77
93
  /**
78
94
  * Unset the cached singleton for a given factory, such that the next call
79
95
  * to `$(factory)` will create a new instance.
80
96
  */
81
97
  unset(factory) {
82
- constants.delete(factory);
98
+ singletons.delete(factory);
99
+ const a = services.get(factory);
100
+ if (a) {
101
+ a.valid = false;
102
+ a.value = void 0;
103
+ }
83
104
  },
84
105
  /**
85
- * Replace the implementation for a given factory, such that future calls to
86
- * `$(factory)` will call or construct the replacement instead.
106
+ * Replace the implementation for a given factory (the "original"), such
107
+ * that future calls to `$(original)` will call or construct the replacement
108
+ * instead.
109
+ *
110
+ * Replacements are run under the same conditions as any other
111
+ * {@link SingletonFactory}: root job, no reactive reads, and generator
112
+ * function auto-{@link fork}()ing. They don't have to be *implemented* the
113
+ * same way, though: the original could be a class, and the replacement a
114
+ * function, or vice versa: they just need to produce something that
115
+ * satisfies the type expected by the original's consumers.
87
116
  *
88
- * If the replacement is omitted, null, or undefined, future calls will
89
- * invoke the original factory again.
117
+ * The replacement can even be a generic wrapper, as the current creation
118
+ * target (i.e. the "original" factory) can be obtained via
119
+ * {@link $cache.creating}(). If the replacement is omitted, null, or
120
+ * undefined, future calls will invoke the original factory again.
90
121
  *
91
122
  * (Note: in all cases the replacement will not take effect if there's
92
- * already a cached singleton, so you may wish to call
123
+ * already a cached singleton, so you may wish to also call
93
124
  * {@link $cache.unset}() to ensure a future call is actually executed.)
94
125
  */
95
- replace(factory, replacement) {
96
- replacement != null && replacement !== factory ? factories.set(factory, replacement) : factories.delete(factory);
126
+ replace(original, replacement) {
127
+ replacement != null && replacement !== original ? factories.set(original, replacement) : factories.delete(original);
128
+ },
129
+ /**
130
+ * The original factory whose singleton `$()` is currently creating, if any.
131
+ * (Only useful for generic {@link replace} factories.)
132
+ *
133
+ * Note: The return value is the original factory argument that was passed
134
+ * to `$()`, not its replacement.
135
+ */
136
+ creating() {
137
+ return stack[0];
97
138
  }
98
139
  };
99
140
 
package/dist/signals.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AnyFunction, O as OptionalCleanup, D as DisposeFn, L as SignalSource, Y as Yielding, S as Source, P as PlainFunction, a as Stream } from './types-pElgImr7.js';
2
- import { U as UntilMethod } from './sinks-B_LfTZgF.js';
1
+ import { A as AnyFunction, O as OptionalCleanup, D as DisposeFn, m as SignalSource, Y as Yielding, S as Source, P as PlainFunction, a as Stream } from './types-C3WWddF2.js';
2
+ import { U as UntilMethod } from './sinks-CoXxbJrs.js';
3
3
 
4
4
  /**
5
5
  * A decorator function that supports both TC39 and "legacy" decorator protocols
package/dist/signals.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { d as root, o as currentCell, g as getJob, m as must, L as currentJob, w as reject, b as resolve } from './jobutils-O9tOTNKC.mjs';
2
- import { C as Cell, r as ruleQueue, d as defaultQ, c as currentRule, p as perSignal, g as getCell } from './hooks-BnMYHRlM.mjs';
3
- export { a as CircularDependency, W as WriteConflict } from './hooks-BnMYHRlM.mjs';
4
- import { d as defer, C as CallableObject, c as apply, s as setMap, b as decorateMethod, i as isFunction, f as isPlainFunction, g as arrayEq } from './utils-BgqyDPjA.mjs';
5
- import { c as callOrWait } from './call-or-wait-CvOF0pao.mjs';
1
+ import { d as root, l as currentCell, g as getJob, m as must, P as currentJob, H as reject, r as resolve } from './jobutils-DYe7dvZO.mjs';
2
+ import { C as Cell, r as ruleQueue, d as defaultQ, c as currentRule, p as perSignal, g as getCell } from './hooks-Bx4Lqd9S.mjs';
3
+ export { a as CircularDependency, W as WriteConflict } from './hooks-Bx4Lqd9S.mjs';
4
+ import { d as defer, C as CallableObject, b as apply, s as setMap, a as decorateMethod, i as isFunction, f as isPlainFunction, g as arrayEq } from './utils-CwhAPj4M.mjs';
5
+ import { c as callOrWait } from './call-or-wait-BTeEcToS.mjs';
6
6
 
7
7
  class RF extends CallableObject {
8
8
  constructor(q) {
@@ -96,9 +96,12 @@ function fx(f, ...args) {
96
96
  let jobs, cell = Cell.mkCached(f);
97
97
  return () => {
98
98
  if (currentCell) {
99
- if (currentCell.isObserved())
99
+ if (currentCell.isObserved()) {
100
+ cell.isObserved() || (cell.validThrough = 0);
100
101
  cell.getValue();
102
+ }
101
103
  } else {
104
+ cell.isObserved() || (cell.validThrough = 0);
102
105
  if (!sosHas(jobs, getJob())) {
103
106
  const job = currentJob;
104
107
  jobs = sosAdd(jobs, job);
@@ -1,4 +1,4 @@
1
- import { h as Suspend, a as Stream, Y as Yielding, b as Sink, I as Inlet, K as Connection, i as RecalcSource } from './types-pElgImr7.js';
1
+ import { n as Suspend, Y as Yielding, a as Stream, b as Sink, I as Inlet, h as Connection, R as RecalcSource } from './types-C3WWddF2.js';
2
2
 
3
3
  /**
4
4
  * The result type returned from calls to {@link Each}.next()
@@ -204,4 +204,4 @@ declare function recalcWhen<T extends WeakKey>(key: T, factory: (key: T) => Reca
204
204
  */
205
205
  declare function isObserved(): boolean | undefined;
206
206
 
207
- export { type EachResult as E, type NextMethod as N, type UntilMethod as U, type Each as a, each as e, forEach as f, isObserved as i, next as n, recalcWhen as r };
207
+ export { type Each as E, type NextMethod as N, type UntilMethod as U, type EachResult as a, each as e, forEach as f, isObserved as i, next as n, recalcWhen as r };
@@ -914,4 +914,4 @@ interface Request<T> {
914
914
  */
915
915
  type RecalcSource = ((cb: () => void) => unknown);
916
916
 
917
- export { type AnyFunction as A, type Backpressure as B, type CleanupFn as C, type DisposeFn as D, ErrorResult as E, backpressure as F, type Throttle as G, type HandledError as H, type Inlet as I, type Job as J, type Connection as K, type SignalSource as L, IsStream as M, type Nothing as N, type OptionalCleanup as O, type PlainFunction as P, connect as Q, type Request as R, type Source as S, type Transformer as T, type UnhandledError as U, ValueResult as V, throttle as W, pipe as X, type Yielding as Y, compose as Z, into as _, type Stream as a, type Sink as b, type StartFn as c, type StartObj as d, type AsyncStart as e, type SyncStart as f, type JobIterator as g, type Suspend as h, type RecalcSource as i, reject as j, resolver as k, rejecter as l, CancelResult as m, noop as n, type JobResult as o, isCancel as p, isValue as q, resolve as r, isError as s, isUnhandled as t, isHandled as u, markHandled as v, getResult as w, fulfillPromise as x, propagateResult as y, CancelError as z };
917
+ export { type AnyFunction as A, type Backpressure as B, type CleanupFn as C, type DisposeFn as D, ErrorResult as E, isValue as F, markHandled as G, type HandledError as H, type Inlet as I, type Job as J, noop as K, pipe as L, propagateResult as M, type Nothing as N, type OptionalCleanup as O, type PlainFunction as P, reject as Q, type RecalcSource as R, type Source as S, type Transformer as T, type UnhandledError as U, ValueResult as V, rejecter as W, resolve as X, type Yielding as Y, resolver as Z, throttle as _, type Stream as a, type Sink as b, type StartFn as c, type StartObj as d, type AsyncStart as e, CancelError as f, CancelResult as g, type Connection as h, IsStream as i, type JobIterator as j, type JobResult as k, type Request as l, type SignalSource as m, type Suspend as n, type SyncStart as o, type Throttle as p, backpressure as q, compose as r, connect as s, fulfillPromise as t, getResult as u, into as v, isCancel as w, isError as x, isHandled as y, isUnhandled as z };
@@ -10,7 +10,7 @@ class _Batch {
10
10
  this._flags = 0 /* Unset */;
11
11
  this.q = /* @__PURE__ */ new Set();
12
12
  this._run = () => {
13
- this._flags &= ~2 /* Scheduled */;
13
+ this._flags &= -3 /* Scheduled */;
14
14
  this.flush();
15
15
  };
16
16
  /** Run all pending items */
@@ -24,7 +24,7 @@ class _Batch {
24
24
  try {
25
25
  this.reap(queue);
26
26
  } finally {
27
- this._flags &= ~1 /* Running */;
27
+ this._flags &= -2 /* Running */;
28
28
  !queue.size || this._flags & 2 /* Scheduled */ || this._sched();
29
29
  }
30
30
  };
@@ -123,4 +123,4 @@ function isGeneratorFunction(fn) {
123
123
  return isFunction(fn) && fn.prototype instanceof GeneratorBase;
124
124
  }
125
125
 
126
- export { CallableObject as C, GeneratorBase as G, isGeneratorFunction as a, decorateMethod as b, apply as c, defer as d, isClass as e, isPlainFunction as f, arrayEq as g, batch as h, isFunction as i, isArray as j, isPlainObject as k, call as l, setMap as s };
126
+ export { CallableObject as C, GeneratorBase as G, decorateMethod as a, apply as b, isGeneratorFunction as c, defer as d, isClass as e, isPlainFunction as f, arrayEq as g, batch as h, isFunction as i, call as j, isArray as k, isPlainObject as l, setMap as s };
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AnyFunction } from './types-pElgImr7.js';
1
+ import { A as AnyFunction } from './types-C3WWddF2.js';
2
2
 
3
3
  /**
4
4
  * A generic batch processing queue, implemented as a set. (So items are
package/dist/utils.mjs CHANGED
@@ -1 +1 @@
1
- export { C as CallableObject, G as GeneratorBase, c as apply, g as arrayEq, h as batch, l as call, b as decorateMethod, j as isArray, e as isClass, i as isFunction, a as isGeneratorFunction, f as isPlainFunction, k as isPlainObject, s as setMap } from './utils-BgqyDPjA.mjs';
1
+ export { C as CallableObject, G as GeneratorBase, b as apply, g as arrayEq, h as batch, j as call, a as decorateMethod, k as isArray, e as isClass, i as isFunction, c as isGeneratorFunction, f as isPlainFunction, l as isPlainObject, s as setMap } from './utils-CwhAPj4M.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uneventful",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Declarative, event-driven reactivity: signals, streams, structured concurrency, and easy resource cleanup",
5
5
  "keywords": [
6
6
  "signals",
@@ -84,7 +84,7 @@
84
84
  "chai-as-promised": "^7.1.1",
85
85
  "cross-env": "^7.0.3",
86
86
  "markdown-it-footnote": "^4.0.0",
87
- "mocha": "^10.2.0",
87
+ "mocha": "^10.8.2",
88
88
  "monkey-around": "^3",
89
89
  "pkgroll": "^2.1.1",
90
90
  "sinon": "^17.0.1",
@@ -92,7 +92,7 @@
92
92
  "ts-expect": "^1.3.0",
93
93
  "ts-node": "^10.9.1",
94
94
  "tsx": "^4.6.1",
95
- "typedoc": "pjeby/typedoc#no-merged-comments",
95
+ "typedoc": "^0.28.17",
96
96
  "typescript": "^5.9.3"
97
97
  }
98
98
  }