sia-reactor 0.0.21 → 0.0.22

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.
Files changed (38) hide show
  1. package/README.md +115 -89
  2. package/dist/{TimeTravelOverlay-CJv-S_Km.d.cts → TimeTravelOverlay-DiXUgbUU.d.cts} +9 -8
  3. package/dist/{TimeTravelOverlay-DxqJL0Zk.d.ts → TimeTravelOverlay-eWjAy0yr.d.ts} +9 -8
  4. package/dist/adapters/react.cjs +66 -84
  5. package/dist/adapters/react.d.cts +23 -22
  6. package/dist/adapters/react.d.ts +23 -22
  7. package/dist/adapters/react.js +3 -3
  8. package/dist/adapters/vanilla.cjs +66 -84
  9. package/dist/adapters/vanilla.d.cts +4 -4
  10. package/dist/adapters/vanilla.d.ts +4 -4
  11. package/dist/adapters/vanilla.js +3 -3
  12. package/dist/{chunk-2WBPGSRL.js → chunk-3SKLWTEA.js} +52 -70
  13. package/dist/{chunk-DP74DVRT.js → chunk-BTA6MIQ6.js} +40 -8
  14. package/dist/{chunk-TFLYCXK4.js → chunk-CS3FOV6J.js} +14 -13
  15. package/dist/{index-Oie9hhE8.d.cts → index-BgbbNXTW.d.cts} +306 -207
  16. package/dist/{index-Oie9hhE8.d.ts → index-BgbbNXTW.d.ts} +306 -207
  17. package/dist/index.cjs +54 -75
  18. package/dist/index.d.cts +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +2 -4
  21. package/dist/{plugins.cjs → modules.cjs} +437 -166
  22. package/dist/modules.d.cts +53 -0
  23. package/dist/modules.d.ts +53 -0
  24. package/dist/modules.js +627 -0
  25. package/dist/super.d.ts +610 -281
  26. package/dist/super.global.js +445 -174
  27. package/dist/timeTravel-CraHdbXZ.d.cts +352 -0
  28. package/dist/timeTravel-YUxRHRgh.d.ts +352 -0
  29. package/dist/utils.cjs +41 -7
  30. package/dist/utils.d.cts +1 -1
  31. package/dist/utils.d.ts +1 -1
  32. package/dist/utils.js +3 -1
  33. package/package.json +6 -6
  34. package/dist/plugins.d.cts +0 -112
  35. package/dist/plugins.d.ts +0 -112
  36. package/dist/plugins.js +0 -370
  37. package/dist/timeTravel-B1vedDQc.d.ts +0 -76
  38. package/dist/timeTravel-WpgWmKu-.d.cts +0 -76
@@ -17,28 +17,34 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/ts/plugins.ts
21
- var plugins_exports = {};
22
- __export(plugins_exports, {
23
- BaseReactorPlugin: () => BaseReactorPlugin,
20
+ // src/ts/modules.ts
21
+ var modules_exports = {};
22
+ __export(modules_exports, {
23
+ AsyncStorageAdapter: () => AsyncStorageAdapter,
24
+ BaseReactorModule: () => BaseReactorModule,
24
25
  BaseStorageAdapter: () => BaseStorageAdapter,
26
+ COOKIE_ADAPTER_BUILD: () => COOKIE_ADAPTER_BUILD,
27
+ CookieAdapter: () => CookieAdapter,
25
28
  INDEXED_DB_ADAPTER_BUILD: () => INDEXED_DB_ADAPTER_BUILD,
26
29
  IndexedDBAdapter: () => IndexedDBAdapter,
27
30
  LocalStorageAdapter: () => LocalStorageAdapter,
28
- MemoryStorageAdapter: () => MemoryStorageAdapter,
29
- PERSIST_PLUGIN_BUILD: () => PERSIST_PLUGIN_BUILD,
30
- PersistPlugin: () => PersistPlugin,
31
+ MemoryAdapter: () => MemoryAdapter,
32
+ PERSIST_MODULE_BUILD: () => PERSIST_MODULE_BUILD,
33
+ PersistModule: () => PersistModule,
34
+ SessionStorageAdapter: () => SessionStorageAdapter,
31
35
  StorageAdapter: () => StorageAdapter,
32
- TIME_TRAVEL_PLUGIN_BUILD: () => TIME_TRAVEL_PLUGIN_BUILD,
33
- TimeTravelPlugin: () => TimeTravelPlugin
36
+ TIME_TRAVEL_MODULE_BUILD: () => TIME_TRAVEL_MODULE_BUILD,
37
+ TimeTravelModule: () => TimeTravelModule
34
38
  });
35
- module.exports = __toCommonJS(plugins_exports);
39
+ module.exports = __toCommonJS(modules_exports);
36
40
 
37
41
  // src/ts/core/consts.ts
38
42
  var CTX = {
39
43
  /** Flag indicating whether the application is running in development mode. */
40
44
  isDevEnv: "undefined" !== typeof process ? process.env.NODE_ENV !== "production" : true,
41
- /** active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
45
+ /** Flag indicating whether a cascade is currently ongoing so reactors can allow all writes. */
46
+ isCascading: false,
47
+ /** Active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
42
48
  autotracker: null
43
49
  };
44
50
  var RAW = /* @__PURE__ */ Symbol.for("S.I.A_RAW");
@@ -50,7 +56,6 @@ var VERSION = /* @__PURE__ */ Symbol.for("S.I.A_VERSION");
50
56
  var SSVERSION = /* @__PURE__ */ Symbol.for("S.I.A_SNAPSHOT_VERSION");
51
57
  var RTR_BATCH = "undefined" !== typeof window ? ("undefined" !== typeof queueMicrotask ? queueMicrotask : setTimeout).bind(window) : "undefined" !== typeof process && process.nextTick ? process.nextTick : setTimeout;
52
58
  var RTR_LOG = console.log.bind(console, "[S.I.A Reactor]");
53
- var EVT_WARN = console.warn.bind(console, "[S.I.A Event]");
54
59
  var EVT_OPTS = { LISTENER: ["capture", "depth", "once", "signal", "immediate"], MEDIATOR: ["lazy", "signal", "immediate"] };
55
60
  var NIL = Object.freeze({});
56
61
  var NOOP = () => {
@@ -149,9 +154,33 @@ function inAny(source, key, separator = ".", keyFunc) {
149
154
  function parseEvtOpts(options, opts, boolOpt = opts[0], result = {}) {
150
155
  return Object.assign(result, "boolean" === typeof options ? { [boolOpt]: options } : options), result;
151
156
  }
157
+ function fanout(a, b, c, d) {
158
+ const isEvtPld = !!a?.target, [state, path, news, olds, opts, type] = isEvtPld ? [a.root, a.currentTarget.path, a.currentTarget.value, a.currentTarget.oldValue, b || NIL, a.type] : [a, b, c, (d || NIL).merge ? getAny(a, b) : NIL, d || NIL], target = path === "*" ? state : getAny(state, path);
159
+ if (isEvtPld && type !== "set" && type !== "delete" || !target || !canHandle(news, opts)) return;
160
+ const prev = CTX.isCascading;
161
+ CTX.isCascading = isEvtPld;
162
+ try {
163
+ const walk = (target2, obj, depth = 1, keys = Object.keys(obj)) => {
164
+ for (let i = 0, len = keys.length; i < len; i++) {
165
+ const val = obj[keys[i]];
166
+ try {
167
+ depth > 1 && canHandle(val, opts) ? walk(target2[keys[i]] ||= {}, val, depth - 1) : target2[keys[i]] = val;
168
+ } catch {
169
+ }
170
+ }
171
+ };
172
+ walk(target, opts.merge && canHandle(olds, opts) ? mergeObjs(olds, news) : news, opts.depth === true ? Infinity : +opts.depth);
173
+ } finally {
174
+ CTX.isCascading = prev;
175
+ }
176
+ }
152
177
  function mergeObjs(o1 = {}, o2 = {}) {
153
- const merged = { ...o1 || {}, ...o2 || {} };
154
- return Object.keys(merged).forEach((k) => isObj(o1?.[k]) && isObj(o2?.[k]) && (merged[k] = mergeObjs(o1[k], o2[k]))), merged;
178
+ const merged = { ...o1 ||= {}, ...o2 ||= {} }, keys = Object.keys(merged);
179
+ for (let i = 0, len = keys.length; i < len; i++) {
180
+ const k = keys[i];
181
+ if (isObj(o1[k]) && isObj(o2[k])) merged[k] = mergeObjs(o1[k], o2[k]);
182
+ }
183
+ return merged;
155
184
  }
156
185
  function getTrailRecords(obj, path, reverse = false) {
157
186
  const parts = path.split("."), chain = [["*", obj, obj]];
@@ -166,7 +195,11 @@ function deepClone(obj, config = NIL, seen = /* @__PURE__ */ new WeakMap()) {
166
195
  const clone = config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {};
167
196
  seen.set(obj, clone);
168
197
  const keys = config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
169
- for (let i = 0, len = keys.length; i < len; i++) clone[keys[i]] = deepClone(obj[keys[i]], config, seen);
198
+ for (let i = 0, len = keys.length; i < len; i++)
199
+ try {
200
+ clone[keys[i]] = deepClone(obj[keys[i]], config, seen);
201
+ } catch {
202
+ }
170
203
  return clone;
171
204
  }
172
205
  function nuke(target) {
@@ -206,35 +239,34 @@ var ReactorEvent = class _ReactorEvent {
206
239
  staticType;
207
240
  /** Original event target context. */
208
241
  target;
209
- /** Root reactive object for this event wave. */
242
+ /** Root reactive object for this event instance wave. */
210
243
  root;
211
- /** Original target path for this event wave. */
244
+ /** Original target path for this event instance wave. */
212
245
  path;
213
246
  /** Current value at the event target path. */
214
247
  value;
215
248
  /** Previous value at the event target path. */
216
249
  oldValue;
217
- /** Whether resolve/reject intent semantics are allowed for this event. */
250
+ /** Whether resolve/reject intent semantics are allowed for this event instance. */
218
251
  rejectable;
219
- /** Whether this event wave can bubble back up to ancestors or just capture down. */
252
+ /** Whether this event instance wave can bubble back up to ancestors or just capture down. */
220
253
  bubbles;
221
254
  /**
222
- * `DOMHighResTimeStamp` for this event payload for native event parity and accuracy.
255
+ * `DOMHighResTimeStamp` for this event instance payload for native event parity and accuracy.
223
256
  * Enable `eventTimeStamps` option, then use this over custom timestamps in listeners for accuracy.
224
257
  * */
225
258
  timestamp;
226
- _warn = NOOP;
259
+ /** The `Reactor` instance that dispatched this event instance. */
260
+ reactor;
227
261
  _resolved = "";
228
262
  _rejected = "";
229
263
  _propagationStopped = false;
230
264
  _immediatePropagationStopped = false;
231
265
  /**
232
266
  * @param payload Source payload for this event instance.
233
- * @param bubbles Whether bubbling is enabled for this wave.
234
- * @param canWarn Whether warning output is enabled.
235
- * @param canStamp Whether timestamping is enabled.
267
+ * @param reactor The `Reactor` instance creating this event instance.
236
268
  */
237
- constructor(payload, bubbles = false, canStamp = false, canWarn = true) {
269
+ constructor(payload, reactor) {
238
270
  this.staticType = this.type = payload.type;
239
271
  this.target = payload.target;
240
272
  this.currentTarget = payload.currentTarget;
@@ -243,9 +275,9 @@ var ReactorEvent = class _ReactorEvent {
243
275
  this.value = payload.target.value;
244
276
  this.oldValue = payload.target.oldValue;
245
277
  this.rejectable = payload.rejectable;
246
- this.bubbles = bubbles;
247
- if (canStamp) this.timestamp = performance.now();
248
- if (canWarn) this._warn = EVT_WARN;
278
+ this.bubbles = !!reactor.config.eventBubbling;
279
+ if (reactor.config.eventTimeStamps) this.timestamp = performance.now();
280
+ this.reactor = reactor;
249
281
  }
250
282
  /** Whether propagation has been stopped. */
251
283
  get propagationStopped() {
@@ -275,9 +307,9 @@ var ReactorEvent = class _ReactorEvent {
275
307
  * @example e.resolve("API Load successful"); // message
276
308
  */
277
309
  resolve(message) {
278
- if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored resolve() call on a non-rejectable ${this.staticType} at "${this.path}"`);
279
- if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this._warn(`[ReactorEvent] Resolving an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
280
- if (this.rejectable) this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`;
310
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`resolve()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
311
+ if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this.reactor.log(`[ReactorEvent] Resolving an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
312
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`}`);
281
313
  }
282
314
  /** Rejection reason for rejectable events. */
283
315
  get rejected() {
@@ -290,9 +322,9 @@ var ReactorEvent = class _ReactorEvent {
290
322
  * @example e.resolve("User is not logged in"); // reason
291
323
  */
292
324
  reject(reason) {
293
- if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored reject() call on a non-rejectable ${this.staticType} at "${this.path}"`);
294
- if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this._warn(`[ReactorEvent] Rejecting an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
295
- if (this.rejectable) this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`;
325
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`reject()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
326
+ if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this.reactor.log(`[ReactorEvent] Rejecting an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
327
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`}`);
296
328
  }
297
329
  /**
298
330
  * Returns event path values from target to root.
@@ -301,24 +333,22 @@ var ReactorEvent = class _ReactorEvent {
301
333
  composedPath() {
302
334
  return getTrailRecords(this.root, this.path, true).map((r) => r[2]);
303
335
  }
304
- get canWarn() {
305
- return this._warn !== NOOP;
306
- }
307
336
  };
308
337
 
309
338
  // src/ts/core/reactor.ts
310
339
  var Reactor = class {
340
+ /** Logger function for this reactor instance, override if desired, `this.canLog = false` resets. */
311
341
  log = NOOP;
342
+ /** The core state object for this reactor instance. */
312
343
  core;
313
344
  // `?:`s | pay the ~800 byte price upfront for what u might never use
314
- plugins;
345
+ /** The modules being used by this reactor. */
346
+ modules;
347
+ /** Configuration options for this reactor instance. */
315
348
  config;
349
+ /** Whether this reactor instance is currently batching updates, a window view into the engine timing */
316
350
  isBatching = false;
317
351
  // Async Batching
318
- isCascading = false;
319
- // Setter Cascading
320
- isLogging = false;
321
- // keeping track so API getter doesn't slow down internal iterations in any way
322
352
  queue;
323
353
  // Tasks to run after flush
324
354
  batch;
@@ -385,7 +415,7 @@ var Reactor = class {
385
415
  safeValue = value?.[RAW] || value;
386
416
  unchanged = this.config.equalityFunction(safeValue, safeOldValue);
387
417
  }
388
- if (!indiffable && unchanged && !this.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
418
+ if (!indiffable && unchanged && !CTX.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
389
419
  if (this.config.set) terminated = (value = this.config.set(object, key2, value, oldValue, receiver, paths)) === TERMINATOR;
390
420
  if (this.setters) {
391
421
  const wildcords = this.setters.get("*");
@@ -539,7 +569,7 @@ var Reactor = class {
539
569
  if (this.queue?.size) for (const task of this.queue) task(), this.queue.delete(task);
540
570
  }
541
571
  wave(path, payload) {
542
- const e = new ReactorEvent(payload, this.config.eventBubbling, this.config.eventTimeStamps, this.isLogging), chain = getTrailRecords(this.core, path);
572
+ const e = new ReactorEvent(payload, this), chain = getTrailRecords(this.core, path);
543
573
  e.eventPhase = ReactorEvent.CAPTURING_PHASE;
544
574
  for (let i = 0; i <= chain.length - 2; i++) {
545
575
  if (e.propagationStopped) break;
@@ -613,8 +643,8 @@ var Reactor = class {
613
643
  return depth;
614
644
  }
615
645
  getContext(path) {
616
- const lastDot = path.lastIndexOf("."), value = getAny(this.core, path), object = lastDot === -1 ? this.core : getAny(this.core, path.slice(0, lastDot));
617
- return { path, value, key: path.slice(lastDot + 1) || "", hadKey: true, object };
646
+ const last = path.lastIndexOf("."), value = getAny(this.core, path), object = last === -1 ? this.core : getAny(this.core, path.slice(0, last));
647
+ return { path, value, key: path.slice(last + 1) || "", hadKey: true, object };
618
648
  }
619
649
  bindSignal(cord, sig) {
620
650
  if (sig) sig.aborted ? cord.clup() : sig.addEventListener("abort", cord.clup, { once: true });
@@ -630,7 +660,11 @@ var Reactor = class {
630
660
  const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj;
631
661
  seen.set(obj, clone);
632
662
  const keys = this.config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
633
- for (let i = 0, len = keys.length; i < len; i++) clone[keys[i]] = this.cloned(obj[keys[i]], raw, seen);
663
+ for (let i = 0, len = keys.length; i < len; i++)
664
+ try {
665
+ clone[keys[i]] = this.cloned(obj[keys[i]], raw, seen);
666
+ } catch {
667
+ }
634
668
  if (!raw && this.config.smartCloning) this.snapCache.set(obj, clone), obj[SSVERSION] = version;
635
669
  return clone;
636
670
  }
@@ -718,7 +752,7 @@ var Reactor = class {
718
752
  * rtr.delete("cache.temp", () => TERMINATOR);
719
753
  */
720
754
  delete(path, callback, options) {
721
- return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path, void 0));
755
+ return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path));
722
756
  }
723
757
  /** Registers a delete mediator for a path that only triggers once. */
724
758
  donce(path, callback, options) {
@@ -735,7 +769,7 @@ var Reactor = class {
735
769
  }
736
770
  /**
737
771
  * Registers a watcher for a path.
738
- * Watch callbacks run synchronously with the operation.
772
+ * Watch callbacks run synchronously with the operation, use leaf paths for reliability as it sees exact sets; no bubbling here.
739
773
  * @param path Path or wildcard path.
740
774
  * @param callback Watch callback.
741
775
  * @param options Sync options.
@@ -784,7 +818,7 @@ var Reactor = class {
784
818
  cord = { cb: callback, capture, depth, once, clup: () => this.off(path, callback, options), lDepth: depth !== void 0 ? this.getDepth(path) : depth };
785
819
  if (immediate && (immediate !== "auto" || inAny(this.core, path))) {
786
820
  const target = this.getContext(path);
787
- callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this.config.eventBubbling, this.isLogging));
821
+ callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this));
788
822
  }
789
823
  (cords ?? (this.listeners.set(path, cords = []), cords)).push(cord);
790
824
  return this.bindSignal(cord, signal);
@@ -815,56 +849,39 @@ var Reactor = class {
815
849
  return this.cloned(arguments.length < 2 ? this.core : branch, raw);
816
850
  }
817
851
  /**
818
- * Cascades object updates into direct child paths.
819
- * @param payload Event or payload source.
820
- * @param objectSafe Merge old/new object values before cascading, don't set for arrays; merger doesn't play nice
821
- * @example
822
- * rtr.on("user", (event) => rtr.cascade(event));
823
- * @example
824
- * rtr.watch("user", (_value, payload) => rtr.cascade(payload));
825
- */
826
- cascade({ type, currentTarget: { path, value: news, oldValue: olds } }, objectSafe = true) {
827
- if (type !== "set" && type !== "delete" || !canHandle(news, this.config) || (objectSafe ? !canHandle(olds, this.config) : false)) return;
828
- const obj = objectSafe ? mergeObjs(olds, news) : news, keys = Object.keys(obj);
829
- this.isCascading = true;
830
- for (let i = 0, len = keys.length; i < len; i++) setAny(this.core, path === "*" ? keys[i] : path + "." + keys[i], obj[keys[i]]);
831
- this.isCascading = false;
832
- }
833
- /**
834
- * Installs a plugin instance.
835
- * @param plugin Plugin instance.
836
- * @returns Current reactor for fluent chaining.
852
+ * Installs a module instance.
853
+ * @param target Module instance.
854
+ * @param id Optional identification tag for this instance in the module.
855
+ * @returns Current `Reactor` instance for fluent chaining.
837
856
  */
838
- plugIn(plugin) {
839
- const name = plugin.constructor.plugName;
840
- this.plugins?.get(name)?.destroy();
841
- return (this.plugins ??= /* @__PURE__ */ new Map()).set(name, (plugin.setup(this), plugin)), this;
857
+ use(target, id) {
858
+ return (this.modules ??= /* @__PURE__ */ new Set()).add(target.setup(this, id)), this;
842
859
  }
843
- /** Resets the reactor to its initial state. */
860
+ /** Resets this reactor instance to its initial state. */
844
861
  reset() {
845
862
  this.getters?.clear(), this.setters?.clear(), this.deleters?.clear(), this.watchers?.clear(), this.listeners?.clear();
846
863
  this.batch?.clear(), this.queue?.clear(), this.isBatching = false;
847
864
  }
848
865
  destroy() {
849
- if (this.plugins) for (const plug of this.plugins.values()) plug.destroy();
866
+ if (this.modules) for (const mdle of this.modules) mdle.destroy();
850
867
  this.reset(), nuke(this);
851
868
  }
852
869
  get canLog() {
853
- return this.isLogging = this.log !== NOOP;
870
+ return this.log !== NOOP;
854
871
  }
855
872
  set canLog(value) {
856
- this.log = (this.isLogging = value) ? RTR_LOG : NOOP;
873
+ this.log = value ? RTR_LOG : NOOP;
857
874
  }
858
- get canTraceLineage() {
859
- return this.config.referenceTracking && !!this.config.lineageTracing;
875
+ get canLineageTrace() {
876
+ return this.config.lineageTracing && this.config.referenceTracking;
860
877
  }
861
878
  get canSmartClone() {
862
- return this.config.referenceTracking && !!this.config.smartCloning;
879
+ return this.config.smartCloning && this.config.referenceTracking;
863
880
  }
864
881
  };
865
882
 
866
883
  // src/ts/core/mixins.ts
867
- var methods = ["tick", "stall", "nostall", "get", "gonce", "noget", "set", "sonce", "noset", "delete", "donce", "nodelete", "watch", "wonce", "nowatch", "on", "once", "off", "snapshot", "cascade", "plugIn", "reset", "destroy"];
884
+ var methods = ["tick", "stall", "nostall", "get", "gonce", "noget", "set", "sonce", "noset", "delete", "donce", "nodelete", "watch", "wonce", "nowatch", "on", "once", "off", "snapshot", "use", "reset", "destroy"];
868
885
  function reactive(target, build, preferences = NIL) {
869
886
  if ("__Reactor__" in target) return target;
870
887
  const descriptors = {}, rtr = getReactor(target, true, build), locks = { enumerable: false, configurable: true, writable: false }, hasAffix = !!(preferences.prefix || preferences.suffix);
@@ -872,7 +889,7 @@ function reactive(target, build, preferences = NIL) {
872
889
  let key = methods[i];
873
890
  if (hasAffix) (preferences.whitelist?.includes(key) ?? true) && (key = `${preferences.prefix || ""}${key}${preferences.suffix || ""}`);
874
891
  else if (preferences.whitelist?.includes(key)) continue;
875
- descriptors[key] = { value: rtr[key].bind(rtr), ...locks };
892
+ descriptors[key] = { value: rtr[methods[i]].bind(rtr), ...locks };
876
893
  }
877
894
  descriptors["__Reactor__"] = { value: rtr, ...locks };
878
895
  return Object.defineProperties(rtr.core, descriptors), rtr.core;
@@ -909,47 +926,75 @@ function guardMethod(fn, onError = (e) => console.error(e)) {
909
926
  });
910
927
  }
911
928
 
912
- // src/ts/plugins/base.ts
913
- var BaseReactorPlugin = class {
914
- static plugName;
929
+ // src/ts/modules/base.ts
930
+ var wpArr = ["*"];
931
+ var BaseReactorModule = class {
932
+ static moduleName;
915
933
  get name() {
916
- return this.constructor.plugName;
934
+ return this.constructor.moduleName;
917
935
  }
918
936
  ac = new AbortController();
919
937
  signal = this.ac.signal;
920
- rtr;
938
+ rtrs = /* @__PURE__ */ new Map();
939
+ rids = /* @__PURE__ */ new WeakMap();
940
+ // for quick 0(1) lookups over iteration
941
+ wired = false;
921
942
  config;
922
943
  state;
923
944
  constructor(config, rtr, state) {
924
945
  guardAllMethods(this, this.guard);
925
- this.rtr = rtr;
926
946
  this.config = isObj(config) ? reactive(config) : config;
927
947
  this.state = isObj(state) ? reactive(state) : state;
948
+ rtr && this.attach(rtr);
949
+ }
950
+ /**
951
+ * Connect to a `Reactor` instance, allows managing multiple reactors if needed.
952
+ * @param target `Reactor` instance or `reactive()` object to connect to.
953
+ * @param id Optional custom id for the reactor, prefer over default implicit index id when managing multiple reactors, supports paths to merge into a single tree.
954
+ * @returns Current `ReactorModule` instance for fluent chaining.
955
+ * @example
956
+ * const mod = new MyModule().attach(state1).attach(state2); // implicit index-based ids by default, add a .setup() or `Reactor.use()` when ready for init.
957
+ * @example
958
+ * const persist = new PersistModule(config).attach(sessState, "session").attach(adminState, "session.admin"); // don't use "*", causes de-serialization issues.
959
+ */
960
+ attach(target, id = this.rtrs.size) {
961
+ const rtr = getReactor(target);
962
+ if (!rtr || this.rtrs.has(id)) return this;
963
+ return this.rids.set((this.rtrs.set(id, rtr), rtr), id), this.onAttach(rtr, id), this;
928
964
  }
929
- /** Entry point called to initialize plugin wiring. */
930
- setup(rtr) {
931
- this.rtr ??= rtr;
932
- this.wire();
965
+ onAttach(_rtr, _rid) {
966
+ }
967
+ /**
968
+ * Entry point called to initialize module wiring, calls `.attach(target, id)` first, `Reactor.use()` calls this internally.
969
+ * Should run as last in `.attach()` chain or after all desired reactors if using multiple; so wiring is done safely after.
970
+ * @param target `Reactor` instance or `reactive()` object to connect to.
971
+ * @param id Optional id for the reactor, prefer over default implicit index id when managing multiple reactors.
972
+ * @returns Current `ReactorModule` instance for fluent chaining.
973
+ * @example
974
+ * const mod = new MyModule().attach(state1).setup(state2); // if using multiple, this should run last; with same params as `.attach()` for a shorter chain
975
+ */
976
+ setup(target, id) {
977
+ return this.attach(target, id), !this.wired && (this.wire(), this.wired = true), this;
933
978
  }
934
979
  destroy() {
935
980
  this.ac.abort();
936
981
  this.onDestroy?.();
937
982
  }
938
983
  /**
939
- * Wraps a function with plugin-scoped error logging.
984
+ * Wraps a function with module-scoped error logging.
940
985
  * Use this when creating functions dynamically (for example, before attaching an anonymous listener on the fly).
941
986
  * @example
942
987
  * window.addEventListener("resize", this.guard(() => this.syncLayout(true)), { signal: this.signal });
943
988
  */
944
989
  guard = (fn) => {
945
- return guardMethod(fn, (e) => this.rtr.log(`[Reactor "${this.name}" Plug] Error: ${e}`));
990
+ return guardMethod(fn, (e) => this.rtrs.values().next().value?.log(`[Reactor "${this.name}" Module] Error: ${e}`));
946
991
  };
947
992
  // `()=>{}`: needs to be bounded even before initialization
948
993
  };
949
994
 
950
995
  // src/ts/utils/store.ts
951
996
  var BaseStorageAdapter = class {
952
- static name;
997
+ name = "StorageAdapter";
953
998
  config;
954
999
  warn = (act = "", mssg = "Support issue or Private Mode", key = "", store = "") => this.config.debug && console.warn(`[${this.constructor.name} \`${act}\`] Failed${key ? `for ${key}` : ""} ${store ? ` on "${store}"` : ""} ${this.config.dbName ? ` at ${this.config.dbName}` : ""} (${mssg})`);
955
1000
  constructor(config) {
@@ -957,26 +1002,44 @@ var BaseStorageAdapter = class {
957
1002
  }
958
1003
  };
959
1004
  var StorageAdapter = class extends BaseStorageAdapter {
1005
+ name = "SyncStorageAdapter";
960
1006
  };
961
1007
  var AsyncStorageAdapter = class extends BaseStorageAdapter {
1008
+ name = "AsyncStorageAdapter";
962
1009
  };
963
1010
  var LocalStorageAdapter = class extends StorageAdapter {
964
- static name = "LocalStorage";
965
- get(key) {
1011
+ name = "LocalStorage";
1012
+ /**
1013
+ * Reads and parses a value from localStorage.
1014
+ * @param key Storage key.
1015
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1016
+ */
1017
+ get(key, reviver = this.config.reviver) {
966
1018
  try {
967
1019
  const v = localStorage.getItem(key);
968
- return v ? JSON.parse(v) : void 0;
1020
+ return v ? JSON.parse(v, reviver) : void 0;
969
1021
  } catch {
970
1022
  return void 0;
971
1023
  }
972
1024
  }
973
- set(key, value) {
1025
+ /**
1026
+ * Serializes and writes a value to localStorage.
1027
+ * @param key Storage key.
1028
+ * @param value Value to serialize.
1029
+ * @returns `true` when write succeeds, else `false`.
1030
+ */
1031
+ set(key, value, replacer = this.config.replacer) {
974
1032
  try {
975
- return localStorage.setItem(key, JSON.stringify(value)), true;
1033
+ return localStorage.setItem(key, JSON.stringify(value, replacer)), true;
976
1034
  } catch (e) {
977
1035
  return this.warn("setItem", void 0, key), false;
978
1036
  }
979
1037
  }
1038
+ /**
1039
+ * Removes a single key from localStorage.
1040
+ * @param key Storage key.
1041
+ * @returns `true` when removal succeeds, else `false`.
1042
+ */
980
1043
  remove(key) {
981
1044
  try {
982
1045
  return localStorage.removeItem(key), true;
@@ -984,6 +1047,10 @@ var LocalStorageAdapter = class extends StorageAdapter {
984
1047
  return this.warn("removeItem", void 0, key), false;
985
1048
  }
986
1049
  }
1050
+ /**
1051
+ * Clears all localStorage entries for the current origin.
1052
+ * @returns `true` when clear succeeds, else `false`.
1053
+ */
987
1054
  clear() {
988
1055
  try {
989
1056
  return localStorage.clear(), true;
@@ -992,25 +1059,94 @@ var LocalStorageAdapter = class extends StorageAdapter {
992
1059
  }
993
1060
  }
994
1061
  };
995
- var MemoryStorageAdapter = class extends StorageAdapter {
1062
+ var SessionStorageAdapter = class extends StorageAdapter {
1063
+ name = "SessionStorage";
1064
+ /**
1065
+ * Reads and parses a value from sessionStorage.
1066
+ * @param key Storage key.
1067
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1068
+ */
1069
+ get(key, reviver = this.config.reviver) {
1070
+ try {
1071
+ const v = sessionStorage.getItem(key);
1072
+ return v ? JSON.parse(v, reviver) : void 0;
1073
+ } catch {
1074
+ return void 0;
1075
+ }
1076
+ }
1077
+ /**
1078
+ * Serializes and writes a value to sessionStorage.
1079
+ * @param key Storage key.
1080
+ * @param value Value to serialize.
1081
+ * @returns `true` when write succeeds, else `false`.
1082
+ */
1083
+ set(key, value, replacer = this.config.replacer) {
1084
+ try {
1085
+ return sessionStorage.setItem(key, JSON.stringify(value, replacer)), true;
1086
+ } catch (e) {
1087
+ return this.warn("setItem", void 0, key), false;
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Removes a single key from sessionStorage.
1092
+ * @param key Storage key.
1093
+ * @returns `true` when removal succeeds, else `false`.
1094
+ */
1095
+ remove(key) {
1096
+ try {
1097
+ return sessionStorage.removeItem(key), true;
1098
+ } catch (e) {
1099
+ return this.warn("removeItem", void 0, key), false;
1100
+ }
1101
+ }
1102
+ /**
1103
+ * Clears all sessionStorage entries for the current tab session.
1104
+ * @returns `true` when clear succeeds, else `false`.
1105
+ */
1106
+ clear() {
1107
+ try {
1108
+ return sessionStorage.clear(), true;
1109
+ } catch (e) {
1110
+ return this.warn("clear", void 0), false;
1111
+ }
1112
+ }
1113
+ };
1114
+ var MemoryAdapter = class extends StorageAdapter {
1115
+ name = "Memory";
996
1116
  constructor(build) {
997
1117
  super({ store: /* @__PURE__ */ new Map(), ...build });
998
1118
  }
999
- get(key) {
1119
+ /**
1120
+ * Reads and parses a value from memory storage.
1121
+ * @param key Storage key.
1122
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1123
+ */
1124
+ get(key, reviver = this.config.reviver) {
1000
1125
  try {
1001
1126
  const v = this.config.store.get(key);
1002
- return v ? JSON.parse(v) : void 0;
1127
+ return v ? JSON.parse(v, reviver) : void 0;
1003
1128
  } catch {
1004
1129
  return void 0;
1005
1130
  }
1006
1131
  }
1007
- set(key, value) {
1132
+ /**
1133
+ * Serializes and writes a value to memory storage.
1134
+ * @param key Storage key.
1135
+ * @param value Value to serialize.
1136
+ * @returns `true` when write succeeds, else `false`.
1137
+ */
1138
+ set(key, value, replacer = this.config.replacer) {
1008
1139
  try {
1009
- return this.config.store.set(key, JSON.stringify(value)), true;
1140
+ return this.config.store.set(key, JSON.stringify(value, replacer)), true;
1010
1141
  } catch (e) {
1011
1142
  return this.warn("set", void 0, key), false;
1012
1143
  }
1013
1144
  }
1145
+ /**
1146
+ * Removes a single key from memory storage.
1147
+ * @param key Storage key.
1148
+ * @returns `true` when removal succeeds, else `false`.
1149
+ */
1014
1150
  remove(key) {
1015
1151
  try {
1016
1152
  return this.config.store.delete(key), true;
@@ -1018,6 +1154,10 @@ var MemoryStorageAdapter = class extends StorageAdapter {
1018
1154
  return this.warn("remove", void 0, key), false;
1019
1155
  }
1020
1156
  }
1157
+ /**
1158
+ * Clears all entries from memory storage.
1159
+ * @returns `true` when clear succeeds, else `false`.
1160
+ */
1021
1161
  clear() {
1022
1162
  try {
1023
1163
  return this.config.store.clear(), true;
@@ -1026,23 +1166,100 @@ var MemoryStorageAdapter = class extends StorageAdapter {
1026
1166
  }
1027
1167
  }
1028
1168
  };
1169
+ var CookieAdapter = class extends StorageAdapter {
1170
+ name = "Cookie";
1171
+ deets = (opts = NIL, _d = opts.domain ?? this.config.domain, _m = opts.maxAge ?? this.config.maxAge, _e = opts.expires ?? this.config.expires) => `Path=${opts.path ?? this.config.path}; SameSite=${opts.sameSite ?? this.config.sameSite}${_d ? `; Domain=${_d}` : ""}${opts.secure ?? this.config.secure ? "; Secure" : ""}${_m !== void 0 ? `; Max-Age=${_m}` : ""}${_e !== void 0 ? `; Expires=${_e instanceof Date ? _e.toUTCString() : _e}` : ""}`;
1172
+ constructor(build) {
1173
+ super({ secure: "undefined" !== typeof window && location.protocol === "https:", ...COOKIE_ADAPTER_BUILD, ...build });
1174
+ }
1175
+ /**
1176
+ * Reads and parses a cookie visible to the current page scope.
1177
+ * @param key Cookie key.
1178
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1179
+ */
1180
+ get(key, reviver = this.config.reviver) {
1181
+ try {
1182
+ const k = encodeURIComponent(key) + "=";
1183
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
1184
+ if (!pair.startsWith(k)) continue;
1185
+ return JSON.parse(decodeURIComponent(pair.slice(k.length)), reviver);
1186
+ }
1187
+ return void 0;
1188
+ } catch {
1189
+ return void 0;
1190
+ }
1191
+ }
1192
+ /**
1193
+ * Writes a cookie with optional per-call scope/lifetime overrides.
1194
+ * @param key Cookie key.
1195
+ * @param value Value to serialize.
1196
+ * @param opts Optional per-call cookie options.
1197
+ * @returns `true` when write succeeds, else `false`.
1198
+ */
1199
+ set(key, value, opts = NIL, replacer = this.config.replacer) {
1200
+ try {
1201
+ return document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(value, replacer))}; ${this.deets(opts)}`, true;
1202
+ } catch {
1203
+ return this.warn("set", void 0, key), false;
1204
+ }
1205
+ }
1206
+ /**
1207
+ * Removes a cookie key using matching scope attributes.
1208
+ * @param key Cookie key.
1209
+ * @param opts Optional per-call scope overrides.
1210
+ * @returns `true` when removal succeeds, else `false`.
1211
+ */
1212
+ remove(key, opts = NIL) {
1213
+ try {
1214
+ return document.cookie = `${encodeURIComponent(key)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`, true;
1215
+ } catch {
1216
+ return this.warn("remove", void 0, key), false;
1217
+ }
1218
+ }
1219
+ /**
1220
+ * Attempts to remove all visible cookie keys for the given scope.
1221
+ * @param opts Optional per-call scope overrides.
1222
+ * @returns `true` when clear succeeds, else `false`.
1223
+ */
1224
+ clear(opts = NIL) {
1225
+ try {
1226
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
1227
+ const idx = pair.indexOf("=");
1228
+ document.cookie = `${idx === -1 ? pair : pair.slice(0, idx)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`;
1229
+ }
1230
+ return true;
1231
+ } catch {
1232
+ return this.warn("clear"), false;
1233
+ }
1234
+ }
1235
+ };
1029
1236
  var IndexedDBAdapter = class extends AsyncStorageAdapter {
1237
+ name = "IndexedDB";
1030
1238
  db;
1031
- warn = (act = "", mssg = "Support issue or Private Mode", store = "", key = "") => console.warn(`[IndexedDB \`${act}\`] Failed${store ? ` for ${key} on "${store}"` : ""} at ${this.config.dbName} (${mssg})`);
1032
1239
  constructor(build) {
1033
1240
  super({ ...INDEXED_DB_ADAPTER_BUILD, ...build });
1034
1241
  }
1242
+ /**
1243
+ * Returns a connected IndexedDB instance, opening it when needed.
1244
+ * @returns Connected database handle.
1245
+ */
1035
1246
  async idb() {
1036
1247
  const idb = this.config.onidb();
1037
1248
  if (idb || this.db) return Promise.resolve(idb || this.db);
1038
1249
  return new Promise((res, rej) => {
1039
1250
  const req = indexedDB.open(this.config.dbName, this.config.version);
1040
1251
  req.onupgradeneeded = (e) => (this.config.onupgradeneeded(req.result, e), this.config.stores.forEach((s) => !req.result.objectStoreNames.contains(s) && req.result.createObjectStore(s)));
1041
- req.onsuccess = (e) => (this.config.onsuccess(req.result, e), req.result.onversionchange = (e2) => (this.warn("update", "Updated in another tab"), this.config.onversionchange(req.result, e2), req.result.close()), res(this.db = req.result));
1252
+ req.onsuccess = (e) => (this.config.onsuccess(req.result, e), req.result.onversionchange = (e2) => (this.config.onversionchange(req.result, e2), this.warn("update", "Updated in another tab"), req.result.close()), res(this.db = req.result));
1042
1253
  req.onerror = (e) => (this.config.onerror(req.error, e), this.warn("open", "Something went wrong"), rej(req.error));
1043
1254
  req.onblocked = (e) => (this.config.onblocked(e), this.warn("open", "Close other tabs for updates"));
1044
1255
  });
1045
1256
  }
1257
+ /**
1258
+ * Reads a value by key from an object store.
1259
+ * @param key Record key.
1260
+ * @param store Optional object-store override.
1261
+ * @returns Stored value, or `undefined` when missing/unreadable.
1262
+ */
1046
1263
  async get(key, store = this.config.stores[0]) {
1047
1264
  try {
1048
1265
  const req = (await this.idb()).transaction(store).objectStore(store).get(key);
@@ -1051,6 +1268,13 @@ var IndexedDBAdapter = class extends AsyncStorageAdapter {
1051
1268
  return this.warn("get", void 0, store), void 0;
1052
1269
  }
1053
1270
  }
1271
+ /**
1272
+ * Writes a value by key into an object store.
1273
+ * @param key Record key.
1274
+ * @param value Value to store.
1275
+ * @param store Optional object-store override.
1276
+ * @returns `true` when write succeeds, else `false`.
1277
+ */
1054
1278
  async set(key, value, store = this.config.stores[0]) {
1055
1279
  try {
1056
1280
  const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).put(value, key);
@@ -1059,6 +1283,12 @@ var IndexedDBAdapter = class extends AsyncStorageAdapter {
1059
1283
  return this.warn("put", void 0, store), false;
1060
1284
  }
1061
1285
  }
1286
+ /**
1287
+ * Deletes a value by key from an object store.
1288
+ * @param key Record key.
1289
+ * @param store Optional object-store override.
1290
+ * @returns `true` when delete succeeds, else `false`.
1291
+ */
1062
1292
  async remove(key, store = this.config.stores[0]) {
1063
1293
  try {
1064
1294
  const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).delete(key);
@@ -1067,6 +1297,11 @@ var IndexedDBAdapter = class extends AsyncStorageAdapter {
1067
1297
  return this.warn("delete", void 0, store), false;
1068
1298
  }
1069
1299
  }
1300
+ /**
1301
+ * Clears one or more object stores.
1302
+ * @param stores Store name or list of store names to clear.
1303
+ * @returns `true` when all clears succeed, else `false`.
1304
+ */
1070
1305
  async clear(stores = this.config.stores) {
1071
1306
  let success = true;
1072
1307
  for (const store of Array.isArray(stores) ? stores : [stores])
@@ -1079,6 +1314,7 @@ var IndexedDBAdapter = class extends AsyncStorageAdapter {
1079
1314
  return success;
1080
1315
  }
1081
1316
  };
1317
+ var COOKIE_ADAPTER_BUILD = { path: "/", sameSite: "Lax", domain: void 0, debug: false };
1082
1318
  var INDEXED_DB_ADAPTER_BUILD = { dbName: "REACTOR_IDB", stores: ["VAULT"], version: 1, onidb: NOOP, onupgradeneeded: NOOP, onversionchange: NOOP, onsuccess: NOOP, onerror: NOOP, onblocked: NOOP };
1083
1319
 
1084
1320
  // src/ts/utils/fn.ts
@@ -1091,93 +1327,123 @@ function setTimeout2(handler, timeout, ...args) {
1091
1327
  return sig.addEventListener("abort", kill, { once: true }), id;
1092
1328
  }
1093
1329
 
1094
- // src/ts/plugins/persist.ts
1095
- var PersistPlugin = class extends BaseReactorPlugin {
1096
- static plugName = "persist";
1330
+ // src/ts/modules/persist.ts
1331
+ var PersistModule = class extends BaseReactorModule {
1332
+ static moduleName = "persist";
1097
1333
  adapter;
1334
+ hydrateSeq = 0;
1098
1335
  saveTimeoutId = 0;
1099
1336
  get payload() {
1100
- const snap = this.config.useSnapshot ? this.rtr.snapshot() : this.rtr.core;
1101
- return this.config.paths ? this.config.paths.reduce((acc, p) => (setAny(acc, p, getAny(snap, p)), acc), {}) : snap;
1337
+ let res = this.rtrs.size > 1 ? {} : void 0;
1338
+ for (const [rid, rtr] of this.rtrs) {
1339
+ const snap = this.config.useSnapshot ? (this.config.useSnapshot === true && (rtr.config.referenceTracking = rtr.config.smartCloning = true), rtr.snapshot()) : rtr.core, val = this.config.paths ? this.config.paths.reduce((acc, p) => (setAny(acc, p, getAny(snap, p)), acc), {}) : snap;
1340
+ this.rtrs.size > 1 ? setAny(res, rid, val) : res = val;
1341
+ }
1342
+ return res;
1102
1343
  }
1103
1344
  constructor(config, rtr) {
1104
- super({ ...PERSIST_PLUGIN_BUILD, ...config }, rtr);
1345
+ super({ ...PERSIST_MODULE_BUILD, ...config }, rtr, { hydrated: false });
1105
1346
  }
1106
1347
  wire() {
1107
1348
  "undefined" !== typeof window && window.addEventListener("pagehide", this.onDestroy, { signal: this.signal });
1108
1349
  "undefined" !== typeof document && document.addEventListener("visibilitychange", () => document.visibilityState === "hidden" && this.onDestroy(), { signal: this.signal });
1109
- this.config.on("adapter", this.handleAdapterChange, { signal: this.signal, immediate: true });
1110
- this.config.on("disabled", this.handleDisabledChange, { signal: this.signal, immediate: true });
1111
- this.config.on("paths", this.handlePathsState, { signal: this.signal, immediate: true });
1350
+ this.config.on("adapter", this.handleAdapter, { signal: this.signal, immediate: true });
1351
+ this.config.on("disabled", this.handleDisabled, { signal: this.signal, immediate: true });
1352
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
1112
1353
  }
1113
- async handleAdapterChange({ value = LocalStorageAdapter }) {
1354
+ onAttach(rtr) {
1355
+ for (const p of this.config.paths ?? wpArr) !this.config.disabled ? rtr.on(p, this.handleSave, { signal: this.signal, immediate: true }) : rtr.off(p, this.handleSave);
1356
+ }
1357
+ async handleAdapter({ value = LocalStorageAdapter }) {
1358
+ const seq = ++this.hydrateSeq;
1114
1359
  if (this.adapter && value === this.adapter.constructor) return;
1360
+ this.state.hydrated = false;
1115
1361
  this.adapter?.remove(this.config.key);
1116
- this.adapter = "function" === typeof value ? new value({ debug: this.rtr.canLog }) : value;
1117
- let saved = this.adapter.get(this.config.key);
1118
- const isAsync = saved instanceof Promise;
1119
- saved = !isAsync ? saved : await saved;
1120
- const set = (p, news, olds) => setAny(this.rtr.core, p, isPOJO(news, this.rtr.config) && isPOJO(olds, this.rtr.config) ? mergeObjs(news, olds) : olds);
1121
- if (saved) for (const p of this.config.paths ?? ["*"]) set(p, getAny(this.rtr.core, p), getAny(saved, p));
1122
- saved && this.rtr.tick(this.config.paths ?? "*");
1123
- }
1124
- handleDisabledChange({ value }) {
1125
- for (const p of this.config.paths ?? ["*"]) this.rtr.off(p, this.throttleSave), !value && this.rtr.on(p, this.throttleSave, { signal: this.signal, immediate: true });
1362
+ this.adapter = "function" === typeof value ? new value({ debug: !!this.rtrs.values().next().value?.canLog }) : value;
1363
+ try {
1364
+ let saved = this.adapter.get(this.config.key);
1365
+ const isAsync = saved instanceof Promise, depth = this.config.fanout ?? isAsync;
1366
+ saved = !isAsync ? saved : await saved;
1367
+ if (seq !== this.hydrateSeq || !saved) return;
1368
+ for (const [rid, rtr] of this.rtrs) {
1369
+ const entry = this.rtrs.size > 1 ? getAny(saved, rid) : saved;
1370
+ if (!entry) continue;
1371
+ const set = (p, news, olds) => (depth ? fanout : setAny)(rtr.core, p, isPOJO(news, rtr.config) && isPOJO(olds, rtr.config) ? mergeObjs(news, olds) : olds, depth ? { depth } : void 0);
1372
+ for (const p of this.config.paths ?? wpArr) set(p, getAny(rtr.core, p), getAny(entry, p));
1373
+ }
1374
+ for (const rtr of this.rtrs.values()) rtr.tick(!depth ? this.config.paths ?? "*" : "*");
1375
+ } catch {
1376
+ } finally {
1377
+ if (seq === this.hydrateSeq) this.state.hydrated = true;
1378
+ }
1379
+ }
1380
+ handleDisabled({ value }) {
1381
+ for (const rtr of this.rtrs.values()) this.onAttach(rtr);
1126
1382
  value && this.adapter?.remove(this.config.key);
1127
1383
  }
1128
- handlePathsState({ value: paths = ["*"], oldValue: prevs = ["*"] }) {
1129
- for (const p of prevs) this.rtr.off(p, this.throttleSave);
1130
- for (const p of paths) this.rtr.off(p, this.throttleSave), !this.config.disabled && this.rtr.on(p, this.throttleSave, { signal: this.signal, immediate: true });
1384
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
1385
+ for (const rtr of this.rtrs.values()) {
1386
+ for (const p of prevs) rtr.off(p, this.handleSave);
1387
+ for (const p of paths) rtr.off(p, this.handleSave), !this.config.disabled && rtr.on(p, this.handleSave, { signal: this.signal, immediate: true });
1388
+ }
1131
1389
  }
1132
- throttleSave() {
1390
+ handleSave(e) {
1391
+ if (!this.state.hydrated) return e.stopImmediatePropagation();
1133
1392
  if (!this.saveTimeoutId) this.saveTimeoutId = setTimeout2(() => (this.adapter.set(this.config.key, this.payload), this.saveTimeoutId = 0), this.config.throttle, this.signal);
1134
1393
  }
1135
- /** Clears persisted payload for this plugin instance and drops any pending save. */
1394
+ /** Clears persisted payload for this module instance and drops any pending save. */
1136
1395
  clear() {
1137
1396
  clearTimeout(this.saveTimeoutId);
1138
- this.saveTimeoutId = -1, this.rtr.stall(() => this.saveTimeoutId = 0);
1397
+ this.saveTimeoutId = -1;
1398
+ for (const rtr of this.rtrs.values()) rtr.stall(() => this.saveTimeoutId = 0);
1139
1399
  this.adapter?.remove(this.config.key);
1140
1400
  }
1141
1401
  onDestroy() {
1142
- !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
1402
+ this.state.hydrated && !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
1143
1403
  }
1144
1404
  };
1145
- var PERSIST_PLUGIN_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
1405
+ var PERSIST_MODULE_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
1146
1406
 
1147
1407
  // src/ts/utils/num.ts
1148
1408
  function clamp(min = 0, val, max = Infinity) {
1149
1409
  return Math.min(Math.max(val, min), max);
1150
1410
  }
1151
1411
 
1152
- // src/ts/plugins/timeTravel.ts
1153
- var TimeTravelPlugin = class extends BaseReactorPlugin {
1154
- static plugName = "timeTravel";
1412
+ // src/ts/modules/timeTravel.ts
1413
+ var TimeTravelModule = class extends BaseReactorModule {
1414
+ static moduleName = "timeTravel";
1155
1415
  lastTimestamp = 0;
1156
1416
  playbackTimeoutId = -1;
1157
1417
  constructor(config, rtr) {
1158
- super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { initialState: null, history: [], currentFrame: 0, paused: true });
1418
+ super({ ...TIME_TRAVEL_MODULE_BUILD, ...config }, rtr, { initialState: {}, history: [], currentFrame: 0, paused: true });
1159
1419
  }
1160
1420
  // ===========================================================================
1161
1421
  // THE FOUNDATION & WIRETAP (Passive Recording)
1162
1422
  // ===========================================================================
1163
1423
  wire() {
1164
- this.rtr.config.referenceTracking = this.rtr.config.smartCloning = this.rtr.config.eventTimeStamps = true;
1165
- if (!this.state.history.length || this.state.initialState == null) this.state.initialState = this.rtr.snapshot();
1166
1424
  this.lastTimestamp = performance.now();
1167
1425
  this.state.set("currentFrame", (v = 0) => clamp(0, v, this.state.history.length), { signal: this.signal, immediate: true });
1168
- this.config.on("paths", this.handlePathsState, { signal: this.signal, immediate: true });
1426
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
1169
1427
  !this.state.paused && this.play();
1170
1428
  }
1171
- handlePathsState({ value: paths = ["*"], oldValue: prevs = ["*"] }) {
1172
- for (const p of prevs) this.rtr.off(p, this.record);
1173
- for (const p of paths) this.rtr.off(p, this.record), this.rtr.on(p, this.record, { signal: this.signal });
1429
+ onAttach(rtr, rid) {
1430
+ rtr.config.referenceTracking = rtr.config.smartCloning = rtr.config.eventTimeStamps = true;
1431
+ if (!this.state.history.length || !this.state.initialState[rid]) this.state.initialState[rid] = rtr.snapshot();
1432
+ for (const p of this.config.paths ?? wpArr) rtr.on(p, this.record, { signal: this.signal });
1433
+ }
1434
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
1435
+ for (const rtr of this.rtrs.values()) {
1436
+ for (const p of prevs) rtr.off(p, this.record);
1437
+ for (const p of paths) rtr.off(p, this.record), rtr.on(p, this.record, { signal: this.signal });
1438
+ }
1174
1439
  }
1175
1440
  /** Chronicling the lifecycle of the system, Captures the essence of every mutation wave that bubbles up. */
1176
- record(e) {
1441
+ record(e, rid = this.rids.get(e.reactor)) {
1177
1442
  if (!this.state.paused) return;
1178
1443
  if (this.state.currentFrame < this.state.history.length) this.state.history = this.state.history.slice(0, this.state.currentFrame);
1179
1444
  if (this.state.history.length >= this.config.maxHistoryLength) this.state.history = this.state.history.slice(1);
1180
- this.state.history.push({ path: e.target.path, value: this.rtr.snapshot(false, e.target.value), oldValue: this.rtr.snapshot(false, e.target.oldValue), type: e.staticType, rejected: e.rejected, timedelta: e.timestamp - this.lastTimestamp, hadKey: e.target.hadKey });
1445
+ this.state.history.push({ path: e.target.path, value: e.reactor.snapshot(false, e.target.value), oldValue: e.reactor.snapshot(false, e.target.oldValue), type: e.staticType, hadKey: e.target.hadKey, deltat: e.timestamp - this.lastTimestamp, rid });
1446
+ if (e.rejected) this.state.history[this.state.history.length - 1].rejected = e.rejected;
1181
1447
  this.state.currentFrame = this.state.history.length;
1182
1448
  this.lastTimestamp = e.timestamp;
1183
1449
  }
@@ -1186,7 +1452,7 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1186
1452
  this.pause();
1187
1453
  this.playbackTimeoutId = -1;
1188
1454
  this.state.history.length = this.state.currentFrame = 0;
1189
- this.state.initialState = this.rtr.snapshot();
1455
+ this.state.initialState = Object.fromEntries(this.rtrs.entries().map(([rid, rtr]) => [rid, rtr.snapshot()]));
1190
1456
  this.lastTimestamp = performance.now();
1191
1457
  }
1192
1458
  // ===========================================================================
@@ -1199,12 +1465,13 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1199
1465
  while (this.state.currentFrame !== target) {
1200
1466
  const e = this.state.history[forward ? this.state.currentFrame : this.state.currentFrame - 1];
1201
1467
  if (!e) break;
1202
- if (forward) e.type === "delete" ? deleteAny(this.rtr.core, e.path) : setAny(this.rtr.core, e.path, deepClone(e.value, this.rtr.config));
1203
- else !e.hadKey ? deleteAny(this.rtr.core, e.path) : setAny(this.rtr.core, e.path, deepClone(e.oldValue, this.rtr.config));
1468
+ const rtr = this.rtrs.get(e.rid) || this.rtrs.values().next().value;
1469
+ if (forward) e.type === "delete" ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.value, rtr.config));
1470
+ else !e.hadKey ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.oldValue, rtr.config));
1204
1471
  forward ? this.state.currentFrame++ : this.state.currentFrame--;
1205
- if (e.rejected) this.rtr.log(`[Reactor ${this.name} Plug] ${forward ? "Replaying" : "Reversing"} REJECTED intent at "${e.path}"`);
1472
+ if (e.rejected) rtr.log(`[Reactor ${this.name} Module] ${forward ? "Replaying" : "Reversing"} REJECTED intent at "${e.path}"`);
1206
1473
  }
1207
- this.rtr.tick();
1474
+ for (const rtr of this.rtrs.values()) rtr.tick();
1208
1475
  if (!keepShield) this.state.paused = true;
1209
1476
  }
1210
1477
  /** Step through time, Moves the playhead and teleports the state. */
@@ -1223,9 +1490,9 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1223
1490
  async automove(forward = true) {
1224
1491
  this.state.paused = false;
1225
1492
  while ((forward ? this.state.currentFrame < this.state.history.length : this.state.currentFrame > 0) && !this.state.paused) {
1226
- const currIndex = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? currIndex + 1 : currIndex - 1];
1493
+ const idx = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? idx + 1 : idx - 1];
1227
1494
  this.jumpTo(this.state.currentFrame + (forward ? 1 : -1), true);
1228
- if (e?.timedelta > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), Math.min(e.timedelta, this.config.maxPlaybackDelay), this.signal));
1495
+ if (e?.deltat > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), Math.min(e.deltat, this.config.maxPlaybackDelay), this.signal));
1229
1496
  }
1230
1497
  this.state.paused = true;
1231
1498
  }
@@ -1243,35 +1510,39 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1243
1510
  try {
1244
1511
  return JSON.stringify(this.state, replacer, space);
1245
1512
  } catch (e) {
1246
- return this.rtr.log(`[Reactor ${this.name} Plug] Failed to export session`), "";
1513
+ return this.rtrs.values().next().value?.log(`[Reactor ${this.name} Module] Failed to export session`), "";
1247
1514
  }
1248
1515
  }
1249
1516
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
1250
- import(json) {
1517
+ import(json, reviver) {
1251
1518
  try {
1252
- setAny(this.state, "*", JSON.parse(json));
1519
+ setAny(this.state, "*", JSON.parse(json, reviver));
1253
1520
  this.lastTimestamp = performance.now();
1254
1521
  const resume = !this.state.paused, target = this.state.currentFrame;
1255
1522
  this.state.paused = false;
1256
- setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config)), this.rtr.tick();
1523
+ for (const [rid, rtr] of this.rtrs) setAny(rtr.core, "*", deepClone(this.state.initialState[rid], rtr.config)), rtr.tick();
1257
1524
  this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
1258
1525
  } catch (e) {
1259
- this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`);
1526
+ this.rtrs.values().next().value?.log(`[Reactor ${this.name} Module] Failed to load session`);
1260
1527
  }
1261
1528
  }
1262
1529
  };
1263
- var TIME_TRAVEL_PLUGIN_BUILD = { maxPlaybackDelay: 2e3 };
1530
+ var TIME_TRAVEL_MODULE_BUILD = { maxPlaybackDelay: 2e3 };
1264
1531
  // Annotate the CommonJS export names for ESM import in node:
1265
1532
  0 && (module.exports = {
1266
- BaseReactorPlugin,
1533
+ AsyncStorageAdapter,
1534
+ BaseReactorModule,
1267
1535
  BaseStorageAdapter,
1536
+ COOKIE_ADAPTER_BUILD,
1537
+ CookieAdapter,
1268
1538
  INDEXED_DB_ADAPTER_BUILD,
1269
1539
  IndexedDBAdapter,
1270
1540
  LocalStorageAdapter,
1271
- MemoryStorageAdapter,
1272
- PERSIST_PLUGIN_BUILD,
1273
- PersistPlugin,
1541
+ MemoryAdapter,
1542
+ PERSIST_MODULE_BUILD,
1543
+ PersistModule,
1544
+ SessionStorageAdapter,
1274
1545
  StorageAdapter,
1275
- TIME_TRAVEL_PLUGIN_BUILD,
1276
- TimeTravelPlugin
1546
+ TIME_TRAVEL_MODULE_BUILD,
1547
+ TimeTravelModule
1277
1548
  });