sia-reactor 0.0.21 → 0.0.23

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-DxqJL0Zk.d.ts → TimeTravelOverlay-Dglcwpg-.d.ts} +9 -8
  3. package/dist/{TimeTravelOverlay-CJv-S_Km.d.cts → TimeTravelOverlay-OjklzuCD.d.cts} +9 -8
  4. package/dist/adapters/react.cjs +74 -91
  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 +74 -91
  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-TFLYCXK4.js → chunk-5JNWC7Z4.js} +14 -13
  13. package/dist/{chunk-DP74DVRT.js → chunk-MKL3JUPO.js} +55 -15
  14. package/dist/{chunk-2WBPGSRL.js → chunk-MSTHQVNK.js} +61 -78
  15. package/dist/{index-Oie9hhE8.d.cts → index-m0aAWxhX.d.cts} +330 -218
  16. package/dist/{index-Oie9hhE8.d.ts → index-m0aAWxhX.d.ts} +330 -218
  17. package/dist/index.cjs +69 -89
  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} +464 -195
  22. package/dist/modules.d.cts +52 -0
  23. package/dist/modules.d.ts +52 -0
  24. package/dist/modules.js +619 -0
  25. package/dist/super.d.ts +642 -298
  26. package/dist/super.global.js +481 -210
  27. package/dist/timeTravel-DExvNb04.d.ts +352 -0
  28. package/dist/timeTravel-DctvcHVt.d.cts +352 -0
  29. package/dist/utils.cjs +59 -14
  30. package/dist/utils.d.cts +1 -1
  31. package/dist/utils.d.ts +1 -1
  32. package/dist/utils.js +7 -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,14 +56,13 @@ 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 = () => {
57
62
  };
58
63
 
59
64
  // src/ts/utils/obj.ts
60
- var arrRx = /^([^\[\]]+)\[(\d+)\]$/;
65
+ var arrRegex = /^([^\[\]]+)\[(\d+)\]$/;
61
66
  function isObj(obj, arraycheck = true) {
62
67
  return "object" === typeof obj && obj !== null && (arraycheck ? !Array.isArray(obj) : true);
63
68
  }
@@ -65,7 +70,7 @@ function isPOJO(obj, config = NIL, typecheck = true) {
65
70
  return (typecheck ? isObj(obj, false) : true) && (config.crossRealms ? Object.prototype.toString.call(obj) === "[object Object]" : obj.constructor === Object);
66
71
  }
67
72
  function canHandle(obj, config = NIL, typecheck = true) {
68
- if (typecheck && !isObj(obj, false)) return false;
73
+ if (typecheck && !isObj(obj, false) || obj[INERTIA]) return false;
69
74
  if (Array.isArray(obj) || !config.preserveContext && isPOJO(obj, config, false)) return true;
70
75
  if (config.preserveContext) return !(obj instanceof Map) && !(obj instanceof Set) && !(obj instanceof WeakMap) && !(obj instanceof WeakSet) && !(obj instanceof Error) && !(obj instanceof Number) && !(obj instanceof Date) && !(obj instanceof String) && !(obj instanceof RegExp) && !(obj instanceof ArrayBuffer) && !(obj instanceof Promise);
71
76
  return false;
@@ -76,7 +81,7 @@ function getAny(source, key, separator = ".", keyFunc) {
76
81
  const keys = key.split(separator);
77
82
  let currObj = source;
78
83
  for (let i = 0, len = keys.length; i < len; i++) {
79
- const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRx);
84
+ const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRegex);
80
85
  if (match) {
81
86
  const [, key3, iStr] = match;
82
87
  if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return void 0;
@@ -93,7 +98,7 @@ function setAny(target, key, value, separator = ".", keyFunc) {
93
98
  if (!key.includes(separator)) return void (target[keyFunc ? keyFunc(key) : key] = value);
94
99
  const keys = key.split(separator);
95
100
  for (let currObj = target, i = 0, len = keys.length; i < len; i++) {
96
- const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRx);
101
+ const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRegex);
97
102
  if (match) {
98
103
  const [, key3, iStr] = match;
99
104
  if (!Array.isArray(currObj[key3])) currObj[key3] = [];
@@ -114,7 +119,7 @@ function deleteAny(target, key, separator = ".", keyFunc) {
114
119
  if (!key.includes(separator)) return void delete target[keyFunc ? keyFunc(key) : key];
115
120
  const keys = key.split(separator);
116
121
  for (let currObj = target, i = 0, len = keys.length; i < len; i++) {
117
- const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRx);
122
+ const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRegex);
118
123
  if (match) {
119
124
  const [, key3, iStr] = match;
120
125
  if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return;
@@ -132,7 +137,7 @@ function inAny(source, key, separator = ".", keyFunc) {
132
137
  if (!key.includes(separator)) return key in source;
133
138
  const keys = key.split(separator);
134
139
  for (let currObj = source, i = 0, len = keys.length; i < len; i++) {
135
- const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRx);
140
+ const key2 = keyFunc ? keyFunc(keys[i]) : keys[i], match = key2.includes("[") && key2.match(arrRegex);
136
141
  if (match) {
137
142
  const [, key3, iStr] = match;
138
143
  if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return false;
@@ -149,9 +154,38 @@ 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
  }
152
- 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;
157
+ function fanout(a, b, c, d) {
158
+ const isEvPd = !!a?.target, isPath = !isEvPd && "string" === typeof b, [state, path, olds, news, opts, type] = isEvPd ? [a.root, a.currentTarget.path, a.currentTarget.oldValue, a.currentTarget.value, b || NIL, a.type] : isPath ? [a, b, getAny(a, b), c, d || NIL, void 0] : [void 0, void 0, a, b, c || NIL, void 0], target = isEvPd ? getAny(a.root, a.currentTarget.path) : isPath ? getAny(state, path) : olds;
159
+ if (isEvPd && type !== "set" && type !== "delete" || !target || !canHandle(news, opts)) return;
160
+ const prev = CTX.isCascading;
161
+ CTX.isCascading = isEvPd;
162
+ try {
163
+ const walk = (target2, obj, depth = isEvPd ? 1 : Infinity, keys = Object.keys(obj)) => {
164
+ for (let i = 0, len = keys.length; i < len; i++) {
165
+ const val = obj[keys[i]];
166
+ try {
167
+ if ((opts.atomic ?? true) && Array.isArray(val)) target2[keys[i]] = val, target2[keys[i]].length = target2[keys[i]].length;
168
+ else depth > 1 && canHandle(val, opts) ? walk(target2[keys[i]] ||= {}, val, depth - 1) : target2[keys[i]] = val;
169
+ } catch (e) {
170
+ if (e instanceof RangeError) throw e;
171
+ }
172
+ }
173
+ };
174
+ if ((opts.atomic ?? true) && Array.isArray(news) && isPath) setAny(state, path, news), getAny(state, path).length = news.length;
175
+ else walk(target, opts.merge ? mergeObjs(olds, news, opts) : news, opts.depth === true ? Infinity : opts.depth);
176
+ } finally {
177
+ CTX.isCascading = prev;
178
+ }
179
+ }
180
+ var fanoutOptsArr = ["merge", "depth", "atomic"];
181
+ function mergeObjs(o1, o2, config, pojocheck = true) {
182
+ if (pojocheck && (!isPOJO(o1 || NIL, config) || !isPOJO(o2 || NIL, config))) return o2;
183
+ const merged = { ...o1 ||= {}, ...o2 ||= {} }, keys = Object.keys(merged);
184
+ for (let i = 0, len = keys.length; i < len; i++) {
185
+ const o1C = o1[keys[i]], o2C = o2[keys[i]];
186
+ if (isPOJO(o1C, config) && isPOJO(o2C, config)) merged[keys[i]] = mergeObjs(o1C, o2C, config, false);
187
+ }
188
+ return merged;
155
189
  }
156
190
  function getTrailRecords(obj, path, reverse = false) {
157
191
  const parts = path.split("."), chain = [["*", obj, obj]];
@@ -166,7 +200,12 @@ function deepClone(obj, config = NIL, seen = /* @__PURE__ */ new WeakMap()) {
166
200
  const clone = config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {};
167
201
  seen.set(obj, clone);
168
202
  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);
203
+ for (let i = 0, len = keys.length; i < len; i++)
204
+ try {
205
+ clone[keys[i]] = deepClone(obj[keys[i]], config, seen);
206
+ } catch (e) {
207
+ if (e instanceof RangeError) throw e;
208
+ }
170
209
  return clone;
171
210
  }
172
211
  function nuke(target) {
@@ -206,35 +245,34 @@ var ReactorEvent = class _ReactorEvent {
206
245
  staticType;
207
246
  /** Original event target context. */
208
247
  target;
209
- /** Root reactive object for this event wave. */
248
+ /** Root reactive object for this event instance wave. */
210
249
  root;
211
- /** Original target path for this event wave. */
250
+ /** Original target path for this event instance wave. */
212
251
  path;
213
252
  /** Current value at the event target path. */
214
253
  value;
215
254
  /** Previous value at the event target path. */
216
255
  oldValue;
217
- /** Whether resolve/reject intent semantics are allowed for this event. */
256
+ /** Whether resolve/reject intent semantics are allowed for this event instance. */
218
257
  rejectable;
219
- /** Whether this event wave can bubble back up to ancestors or just capture down. */
258
+ /** Whether this event instance wave can bubble back up to ancestors or just capture down. */
220
259
  bubbles;
221
260
  /**
222
- * `DOMHighResTimeStamp` for this event payload for native event parity and accuracy.
261
+ * `DOMHighResTimeStamp` for this event instance payload for native event parity and accuracy.
223
262
  * Enable `eventTimeStamps` option, then use this over custom timestamps in listeners for accuracy.
224
263
  * */
225
264
  timestamp;
226
- _warn = NOOP;
265
+ /** The `Reactor` instance that dispatched this event instance. */
266
+ reactor;
227
267
  _resolved = "";
228
268
  _rejected = "";
229
269
  _propagationStopped = false;
230
270
  _immediatePropagationStopped = false;
231
271
  /**
232
272
  * @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.
273
+ * @param reactor The `Reactor` instance creating this event instance.
236
274
  */
237
- constructor(payload, bubbles = false, canStamp = false, canWarn = true) {
275
+ constructor(payload, reactor) {
238
276
  this.staticType = this.type = payload.type;
239
277
  this.target = payload.target;
240
278
  this.currentTarget = payload.currentTarget;
@@ -243,9 +281,9 @@ var ReactorEvent = class _ReactorEvent {
243
281
  this.value = payload.target.value;
244
282
  this.oldValue = payload.target.oldValue;
245
283
  this.rejectable = payload.rejectable;
246
- this.bubbles = bubbles;
247
- if (canStamp) this.timestamp = performance.now();
248
- if (canWarn) this._warn = EVT_WARN;
284
+ this.bubbles = !!reactor.config.eventBubbling;
285
+ if (reactor.config.eventTimeStamps) this.timestamp = performance.now();
286
+ this.reactor = reactor;
249
287
  }
250
288
  /** Whether propagation has been stopped. */
251
289
  get propagationStopped() {
@@ -275,9 +313,9 @@ var ReactorEvent = class _ReactorEvent {
275
313
  * @example e.resolve("API Load successful"); // message
276
314
  */
277
315
  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}"`;
316
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`resolve()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
317
+ 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.`);
318
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`}`);
281
319
  }
282
320
  /** Rejection reason for rejectable events. */
283
321
  get rejected() {
@@ -290,9 +328,9 @@ var ReactorEvent = class _ReactorEvent {
290
328
  * @example e.resolve("User is not logged in"); // reason
291
329
  */
292
330
  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}"`;
331
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`reject()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
332
+ 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.`);
333
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`}`);
296
334
  }
297
335
  /**
298
336
  * Returns event path values from target to root.
@@ -301,24 +339,22 @@ var ReactorEvent = class _ReactorEvent {
301
339
  composedPath() {
302
340
  return getTrailRecords(this.root, this.path, true).map((r) => r[2]);
303
341
  }
304
- get canWarn() {
305
- return this._warn !== NOOP;
306
- }
307
342
  };
308
343
 
309
344
  // src/ts/core/reactor.ts
310
345
  var Reactor = class {
346
+ /** Logger function for this reactor instance, override if desired, `this.canLog = false` resets. */
311
347
  log = NOOP;
348
+ /** The core state object for this reactor instance. */
312
349
  core;
313
350
  // `?:`s | pay the ~800 byte price upfront for what u might never use
314
- plugins;
351
+ /** The modules being used by this reactor. */
352
+ modules;
353
+ /** Configuration options for this reactor instance. */
315
354
  config;
355
+ /** Whether this reactor instance is currently batching updates, a window view into the engine timing */
316
356
  isBatching = false;
317
357
  // 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
358
  queue;
323
359
  // Tasks to run after flush
324
360
  batch;
@@ -351,7 +387,7 @@ var Reactor = class {
351
387
  if (this.config.referenceTracking && parent && key && !this.link(target, parent, key, false)) return target;
352
388
  const cached = this.proxyCache.get(target);
353
389
  if (cached) return cached;
354
- if (target[INERTIA] || !canHandle(target, this.config, false)) return target;
390
+ if (!canHandle(target, this.config, false)) return target;
355
391
  rejectable ||= target[REJECTABLE];
356
392
  indiffable ||= target[INDIFFABLE];
357
393
  const proxy = new Proxy(target, {
@@ -385,7 +421,7 @@ var Reactor = class {
385
421
  safeValue = value?.[RAW] || value;
386
422
  unchanged = this.config.equalityFunction(safeValue, safeOldValue);
387
423
  }
388
- if (!indiffable && unchanged && !this.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
424
+ if (!indiffable && unchanged && !CTX.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
389
425
  if (this.config.set) terminated = (value = this.config.set(object, key2, value, oldValue, receiver, paths)) === TERMINATOR;
390
426
  if (this.setters) {
391
427
  const wildcords = this.setters.get("*");
@@ -539,7 +575,7 @@ var Reactor = class {
539
575
  if (this.queue?.size) for (const task of this.queue) task(), this.queue.delete(task);
540
576
  }
541
577
  wave(path, payload) {
542
- const e = new ReactorEvent(payload, this.config.eventBubbling, this.config.eventTimeStamps, this.isLogging), chain = getTrailRecords(this.core, path);
578
+ const e = new ReactorEvent(payload, this), chain = getTrailRecords(this.core, path);
543
579
  e.eventPhase = ReactorEvent.CAPTURING_PHASE;
544
580
  for (let i = 0; i <= chain.length - 2; i++) {
545
581
  if (e.propagationStopped) break;
@@ -613,8 +649,8 @@ var Reactor = class {
613
649
  return depth;
614
650
  }
615
651
  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 };
652
+ const last = path.lastIndexOf("."), value = getAny(this.core, path), object = last === -1 ? this.core : getAny(this.core, path.slice(0, last));
653
+ return { path, value, key: path.slice(last + 1) || "", hadKey: true, object };
618
654
  }
619
655
  bindSignal(cord, sig) {
620
656
  if (sig) sig.aborted ? cord.clup() : sig.addEventListener("abort", cord.clup, { once: true });
@@ -630,7 +666,12 @@ var Reactor = class {
630
666
  const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj;
631
667
  seen.set(obj, clone);
632
668
  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);
669
+ for (let i = 0, len = keys.length; i < len; i++)
670
+ try {
671
+ clone[keys[i]] = this.cloned(obj[keys[i]], raw, seen);
672
+ } catch (e) {
673
+ if (e instanceof RangeError) throw e;
674
+ }
634
675
  if (!raw && this.config.smartCloning) this.snapCache.set(obj, clone), obj[SSVERSION] = version;
635
676
  return clone;
636
677
  }
@@ -718,7 +759,7 @@ var Reactor = class {
718
759
  * rtr.delete("cache.temp", () => TERMINATOR);
719
760
  */
720
761
  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));
762
+ return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path));
722
763
  }
723
764
  /** Registers a delete mediator for a path that only triggers once. */
724
765
  donce(path, callback, options) {
@@ -735,7 +776,7 @@ var Reactor = class {
735
776
  }
736
777
  /**
737
778
  * Registers a watcher for a path.
738
- * Watch callbacks run synchronously with the operation.
779
+ * Watch callbacks run synchronously with the operation, use leaf paths for reliability as it sees exact sets; no bubbling here.
739
780
  * @param path Path or wildcard path.
740
781
  * @param callback Watch callback.
741
782
  * @param options Sync options.
@@ -784,7 +825,7 @@ var Reactor = class {
784
825
  cord = { cb: callback, capture, depth, once, clup: () => this.off(path, callback, options), lDepth: depth !== void 0 ? this.getDepth(path) : depth };
785
826
  if (immediate && (immediate !== "auto" || inAny(this.core, path))) {
786
827
  const target = this.getContext(path);
787
- callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this.config.eventBubbling, this.isLogging));
828
+ callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this));
788
829
  }
789
830
  (cords ?? (this.listeners.set(path, cords = []), cords)).push(cord);
790
831
  return this.bindSignal(cord, signal);
@@ -815,56 +856,39 @@ var Reactor = class {
815
856
  return this.cloned(arguments.length < 2 ? this.core : branch, raw);
816
857
  }
817
858
  /**
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.
859
+ * Installs a module instance.
860
+ * @param target Module instance.
861
+ * @param id Optional identification tag for this instance in the module.
862
+ * @returns Current `Reactor` instance for fluent chaining.
837
863
  */
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;
864
+ use(target, id) {
865
+ return (this.modules ??= /* @__PURE__ */ new Set()).add(target.setup(this, id)), this;
842
866
  }
843
- /** Resets the reactor to its initial state. */
867
+ /** Resets this reactor instance to its initial state. */
844
868
  reset() {
845
869
  this.getters?.clear(), this.setters?.clear(), this.deleters?.clear(), this.watchers?.clear(), this.listeners?.clear();
846
870
  this.batch?.clear(), this.queue?.clear(), this.isBatching = false;
847
871
  }
848
872
  destroy() {
849
- if (this.plugins) for (const plug of this.plugins.values()) plug.destroy();
873
+ if (this.modules) for (const mdle of this.modules) mdle.destroy();
850
874
  this.reset(), nuke(this);
851
875
  }
852
876
  get canLog() {
853
- return this.isLogging = this.log !== NOOP;
877
+ return this.log !== NOOP;
854
878
  }
855
879
  set canLog(value) {
856
- this.log = (this.isLogging = value) ? RTR_LOG : NOOP;
880
+ this.log = value ? RTR_LOG : NOOP;
857
881
  }
858
- get canTraceLineage() {
859
- return this.config.referenceTracking && !!this.config.lineageTracing;
882
+ get canLineageTrace() {
883
+ return this.config.lineageTracing && this.config.referenceTracking;
860
884
  }
861
885
  get canSmartClone() {
862
- return this.config.referenceTracking && !!this.config.smartCloning;
886
+ return this.config.smartCloning && this.config.referenceTracking;
863
887
  }
864
888
  };
865
889
 
866
890
  // 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"];
891
+ 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
892
  function reactive(target, build, preferences = NIL) {
869
893
  if ("__Reactor__" in target) return target;
870
894
  const descriptors = {}, rtr = getReactor(target, true, build), locks = { enumerable: false, configurable: true, writable: false }, hasAffix = !!(preferences.prefix || preferences.suffix);
@@ -872,7 +896,7 @@ function reactive(target, build, preferences = NIL) {
872
896
  let key = methods[i];
873
897
  if (hasAffix) (preferences.whitelist?.includes(key) ?? true) && (key = `${preferences.prefix || ""}${key}${preferences.suffix || ""}`);
874
898
  else if (preferences.whitelist?.includes(key)) continue;
875
- descriptors[key] = { value: rtr[key].bind(rtr), ...locks };
899
+ descriptors[key] = { value: rtr[methods[i]].bind(rtr), ...locks };
876
900
  }
877
901
  descriptors["__Reactor__"] = { value: rtr, ...locks };
878
902
  return Object.defineProperties(rtr.core, descriptors), rtr.core;
@@ -909,47 +933,75 @@ function guardMethod(fn, onError = (e) => console.error(e)) {
909
933
  });
910
934
  }
911
935
 
912
- // src/ts/plugins/base.ts
913
- var BaseReactorPlugin = class {
914
- static plugName;
936
+ // src/ts/modules/base.ts
937
+ var wpArr = ["*"];
938
+ var BaseReactorModule = class {
939
+ static moduleName;
915
940
  get name() {
916
- return this.constructor.plugName;
941
+ return this.constructor.moduleName;
917
942
  }
918
943
  ac = new AbortController();
919
944
  signal = this.ac.signal;
920
- rtr;
945
+ rtrs = /* @__PURE__ */ new Map();
946
+ rids = /* @__PURE__ */ new WeakMap();
947
+ // for quick 0(1) lookups over iteration
948
+ wired = false;
921
949
  config;
922
950
  state;
923
951
  constructor(config, rtr, state) {
924
952
  guardAllMethods(this, this.guard);
925
- this.rtr = rtr;
926
953
  this.config = isObj(config) ? reactive(config) : config;
927
954
  this.state = isObj(state) ? reactive(state) : state;
955
+ rtr && this.attach(rtr);
928
956
  }
929
- /** Entry point called to initialize plugin wiring. */
930
- setup(rtr) {
931
- this.rtr ??= rtr;
932
- this.wire();
957
+ /**
958
+ * Connect to a `Reactor` instance, allows managing multiple reactors if needed.
959
+ * @param target `Reactor` instance or `reactive()` object to connect to.
960
+ * @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.
961
+ * @returns Current `ReactorModule` instance for fluent chaining.
962
+ * @example
963
+ * const mod = new MyModule().attach(state1).attach(state2); // implicit index-based ids by default, add a .setup() or `Reactor.use()` when ready for init.
964
+ * @example
965
+ * const persist = new PersistModule(config).attach(sessState, "session").attach(adminState, "session.admin"); // don't use "*", causes de-serialization issues.
966
+ */
967
+ attach(target, id = this.rtrs.size) {
968
+ const rtr = getReactor(target);
969
+ if (!rtr || this.rtrs.has(id)) return this;
970
+ return this.rids.set((this.rtrs.set(id, rtr), rtr), id), this.onAttach(rtr, id), this;
971
+ }
972
+ onAttach(_rtr, _rid) {
973
+ }
974
+ /**
975
+ * Entry point called to initialize module wiring, calls `.attach(target, id)` first, `Reactor.use()` calls this internally.
976
+ * Should run as last in `.attach()` chain or after all desired reactors if using multiple; so wiring is done safely after.
977
+ * @param target `Reactor` instance or `reactive()` object to connect to.
978
+ * @param id Optional id for the reactor, prefer over default implicit index id when managing multiple reactors.
979
+ * @returns Current `ReactorModule` instance for fluent chaining.
980
+ * @example
981
+ * const mod = new MyModule().attach(state1).setup(state2); // if using multiple, this should run last; with same params as `.attach()` for a shorter chain
982
+ */
983
+ setup(target, id) {
984
+ return this.attach(target, id), !this.wired && (this.wire(), this.wired = true), this;
933
985
  }
934
986
  destroy() {
935
987
  this.ac.abort();
936
988
  this.onDestroy?.();
937
989
  }
938
990
  /**
939
- * Wraps a function with plugin-scoped error logging.
991
+ * Wraps a function with module-scoped error logging.
940
992
  * Use this when creating functions dynamically (for example, before attaching an anonymous listener on the fly).
941
993
  * @example
942
994
  * window.addEventListener("resize", this.guard(() => this.syncLayout(true)), { signal: this.signal });
943
995
  */
944
996
  guard = (fn) => {
945
- return guardMethod(fn, (e) => this.rtr.log(`[Reactor "${this.name}" Plug] Error: ${e}`));
997
+ return guardMethod(fn, (e) => this.rtrs.values().next().value?.log(`[Reactor "${this.name}" Module] Error: ${e}`));
946
998
  };
947
999
  // `()=>{}`: needs to be bounded even before initialization
948
1000
  };
949
1001
 
950
1002
  // src/ts/utils/store.ts
951
1003
  var BaseStorageAdapter = class {
952
- static name;
1004
+ name = "StorageAdapter";
953
1005
  config;
954
1006
  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
1007
  constructor(config) {
@@ -957,26 +1009,44 @@ var BaseStorageAdapter = class {
957
1009
  }
958
1010
  };
959
1011
  var StorageAdapter = class extends BaseStorageAdapter {
1012
+ name = "SyncStorageAdapter";
960
1013
  };
961
1014
  var AsyncStorageAdapter = class extends BaseStorageAdapter {
1015
+ name = "AsyncStorageAdapter";
962
1016
  };
963
1017
  var LocalStorageAdapter = class extends StorageAdapter {
964
- static name = "LocalStorage";
965
- get(key) {
1018
+ name = "LocalStorage";
1019
+ /**
1020
+ * Reads and parses a value from localStorage.
1021
+ * @param key Storage key.
1022
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1023
+ */
1024
+ get(key, reviver = this.config.reviver) {
966
1025
  try {
967
1026
  const v = localStorage.getItem(key);
968
- return v ? JSON.parse(v) : void 0;
1027
+ return v ? JSON.parse(v, reviver) : void 0;
969
1028
  } catch {
970
1029
  return void 0;
971
1030
  }
972
1031
  }
973
- set(key, value) {
1032
+ /**
1033
+ * Serializes and writes a value to localStorage.
1034
+ * @param key Storage key.
1035
+ * @param value Value to serialize.
1036
+ * @returns `true` when write succeeds, else `false`.
1037
+ */
1038
+ set(key, value, replacer = this.config.replacer) {
974
1039
  try {
975
- return localStorage.setItem(key, JSON.stringify(value)), true;
1040
+ return localStorage.setItem(key, JSON.stringify(value, replacer)), true;
976
1041
  } catch (e) {
977
1042
  return this.warn("setItem", void 0, key), false;
978
1043
  }
979
1044
  }
1045
+ /**
1046
+ * Removes a single key from localStorage.
1047
+ * @param key Storage key.
1048
+ * @returns `true` when removal succeeds, else `false`.
1049
+ */
980
1050
  remove(key) {
981
1051
  try {
982
1052
  return localStorage.removeItem(key), true;
@@ -984,6 +1054,10 @@ var LocalStorageAdapter = class extends StorageAdapter {
984
1054
  return this.warn("removeItem", void 0, key), false;
985
1055
  }
986
1056
  }
1057
+ /**
1058
+ * Clears all localStorage entries for the current origin.
1059
+ * @returns `true` when clear succeeds, else `false`.
1060
+ */
987
1061
  clear() {
988
1062
  try {
989
1063
  return localStorage.clear(), true;
@@ -992,25 +1066,94 @@ var LocalStorageAdapter = class extends StorageAdapter {
992
1066
  }
993
1067
  }
994
1068
  };
995
- var MemoryStorageAdapter = class extends StorageAdapter {
1069
+ var SessionStorageAdapter = class extends StorageAdapter {
1070
+ name = "SessionStorage";
1071
+ /**
1072
+ * Reads and parses a value from sessionStorage.
1073
+ * @param key Storage key.
1074
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1075
+ */
1076
+ get(key, reviver = this.config.reviver) {
1077
+ try {
1078
+ const v = sessionStorage.getItem(key);
1079
+ return v ? JSON.parse(v, reviver) : void 0;
1080
+ } catch {
1081
+ return void 0;
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Serializes and writes a value to sessionStorage.
1086
+ * @param key Storage key.
1087
+ * @param value Value to serialize.
1088
+ * @returns `true` when write succeeds, else `false`.
1089
+ */
1090
+ set(key, value, replacer = this.config.replacer) {
1091
+ try {
1092
+ return sessionStorage.setItem(key, JSON.stringify(value, replacer)), true;
1093
+ } catch (e) {
1094
+ return this.warn("setItem", void 0, key), false;
1095
+ }
1096
+ }
1097
+ /**
1098
+ * Removes a single key from sessionStorage.
1099
+ * @param key Storage key.
1100
+ * @returns `true` when removal succeeds, else `false`.
1101
+ */
1102
+ remove(key) {
1103
+ try {
1104
+ return sessionStorage.removeItem(key), true;
1105
+ } catch (e) {
1106
+ return this.warn("removeItem", void 0, key), false;
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Clears all sessionStorage entries for the current tab session.
1111
+ * @returns `true` when clear succeeds, else `false`.
1112
+ */
1113
+ clear() {
1114
+ try {
1115
+ return sessionStorage.clear(), true;
1116
+ } catch (e) {
1117
+ return this.warn("clear", void 0), false;
1118
+ }
1119
+ }
1120
+ };
1121
+ var MemoryAdapter = class extends StorageAdapter {
1122
+ name = "Memory";
996
1123
  constructor(build) {
997
1124
  super({ store: /* @__PURE__ */ new Map(), ...build });
998
1125
  }
999
- get(key) {
1126
+ /**
1127
+ * Reads and parses a value from memory storage.
1128
+ * @param key Storage key.
1129
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1130
+ */
1131
+ get(key, reviver = this.config.reviver) {
1000
1132
  try {
1001
1133
  const v = this.config.store.get(key);
1002
- return v ? JSON.parse(v) : void 0;
1134
+ return v ? JSON.parse(v, reviver) : void 0;
1003
1135
  } catch {
1004
1136
  return void 0;
1005
1137
  }
1006
1138
  }
1007
- set(key, value) {
1139
+ /**
1140
+ * Serializes and writes a value to memory storage.
1141
+ * @param key Storage key.
1142
+ * @param value Value to serialize.
1143
+ * @returns `true` when write succeeds, else `false`.
1144
+ */
1145
+ set(key, value, replacer = this.config.replacer) {
1008
1146
  try {
1009
- return this.config.store.set(key, JSON.stringify(value)), true;
1147
+ return this.config.store.set(key, JSON.stringify(value, replacer)), true;
1010
1148
  } catch (e) {
1011
1149
  return this.warn("set", void 0, key), false;
1012
1150
  }
1013
1151
  }
1152
+ /**
1153
+ * Removes a single key from memory storage.
1154
+ * @param key Storage key.
1155
+ * @returns `true` when removal succeeds, else `false`.
1156
+ */
1014
1157
  remove(key) {
1015
1158
  try {
1016
1159
  return this.config.store.delete(key), true;
@@ -1018,6 +1161,10 @@ var MemoryStorageAdapter = class extends StorageAdapter {
1018
1161
  return this.warn("remove", void 0, key), false;
1019
1162
  }
1020
1163
  }
1164
+ /**
1165
+ * Clears all entries from memory storage.
1166
+ * @returns `true` when clear succeeds, else `false`.
1167
+ */
1021
1168
  clear() {
1022
1169
  try {
1023
1170
  return this.config.store.clear(), true;
@@ -1026,52 +1173,147 @@ var MemoryStorageAdapter = class extends StorageAdapter {
1026
1173
  }
1027
1174
  }
1028
1175
  };
1176
+ var CookieAdapter = class extends StorageAdapter {
1177
+ name = "Cookie";
1178
+ 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}` : ""}`;
1179
+ constructor(build) {
1180
+ super({ secure: "undefined" !== typeof window && location.protocol === "https:", ...COOKIE_ADAPTER_BUILD, ...build });
1181
+ }
1182
+ /**
1183
+ * Reads and parses a cookie visible to the current page scope.
1184
+ * @param key Cookie key.
1185
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1186
+ */
1187
+ get(key, reviver = this.config.reviver) {
1188
+ try {
1189
+ const k = encodeURIComponent(key) + "=";
1190
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
1191
+ if (!pair.startsWith(k)) continue;
1192
+ return JSON.parse(decodeURIComponent(pair.slice(k.length)), reviver);
1193
+ }
1194
+ return void 0;
1195
+ } catch {
1196
+ return void 0;
1197
+ }
1198
+ }
1199
+ /**
1200
+ * Writes a cookie with optional per-call scope/lifetime overrides.
1201
+ * @param key Cookie key.
1202
+ * @param value Value to serialize.
1203
+ * @param opts Optional per-call cookie options.
1204
+ * @returns `true` when write succeeds, else `false`.
1205
+ */
1206
+ set(key, value, opts, replacer = this.config.replacer) {
1207
+ try {
1208
+ return document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(value, replacer))}; ${this.deets(opts)}`, true;
1209
+ } catch {
1210
+ return this.warn("set", void 0, key), false;
1211
+ }
1212
+ }
1213
+ /**
1214
+ * Removes a cookie key using matching scope attributes.
1215
+ * @param key Cookie key.
1216
+ * @param opts Optional per-call scope overrides.
1217
+ * @returns `true` when removal succeeds, else `false`.
1218
+ */
1219
+ remove(key, opts) {
1220
+ try {
1221
+ return document.cookie = `${encodeURIComponent(key)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`, true;
1222
+ } catch {
1223
+ return this.warn("remove", void 0, key), false;
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Attempts to remove all visible cookie keys for the given scope.
1228
+ * @param opts Optional per-call scope overrides.
1229
+ * @returns `true` when clear succeeds, else `false`.
1230
+ */
1231
+ clear(opts) {
1232
+ try {
1233
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
1234
+ const idx = pair.indexOf("=");
1235
+ document.cookie = `${idx === -1 ? pair : pair.slice(0, idx)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`;
1236
+ }
1237
+ return true;
1238
+ } catch {
1239
+ return this.warn("clear"), false;
1240
+ }
1241
+ }
1242
+ };
1029
1243
  var IndexedDBAdapter = class extends AsyncStorageAdapter {
1244
+ name = "IndexedDB";
1030
1245
  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
1246
  constructor(build) {
1033
1247
  super({ ...INDEXED_DB_ADAPTER_BUILD, ...build });
1034
1248
  }
1249
+ /**
1250
+ * Returns a connected IndexedDB instance, opening it when needed.
1251
+ * @returns Connected database handle.
1252
+ */
1035
1253
  async idb() {
1036
1254
  const idb = this.config.onidb();
1037
1255
  if (idb || this.db) return Promise.resolve(idb || this.db);
1038
1256
  return new Promise((res, rej) => {
1039
1257
  const req = indexedDB.open(this.config.dbName, this.config.version);
1040
1258
  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));
1259
+ 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
1260
  req.onerror = (e) => (this.config.onerror(req.error, e), this.warn("open", "Something went wrong"), rej(req.error));
1043
1261
  req.onblocked = (e) => (this.config.onblocked(e), this.warn("open", "Close other tabs for updates"));
1044
1262
  });
1045
1263
  }
1046
- async get(key, store = this.config.stores[0]) {
1264
+ /**
1265
+ * Reads a value by key from an object store.
1266
+ * @param key Record key.
1267
+ * @param store Optional object-store override.
1268
+ * @returns Stored value, or `undefined` when missing/unreadable.
1269
+ */
1270
+ async get(key, store = this.config.stores[0], options = this.config) {
1047
1271
  try {
1048
- const req = (await this.idb()).transaction(store).objectStore(store).get(key);
1272
+ const req = (await this.idb()).transaction(store, "readonly", options).objectStore(store).get(key);
1049
1273
  return new Promise((res) => req.onsuccess = () => res(req.result));
1050
1274
  } catch {
1051
1275
  return this.warn("get", void 0, store), void 0;
1052
1276
  }
1053
1277
  }
1054
- async set(key, value, store = this.config.stores[0]) {
1278
+ /**
1279
+ * Writes a value by key into an object store.
1280
+ * @param key Record key.
1281
+ * @param value Value to store.
1282
+ * @param store Optional object-store override.
1283
+ * @returns `true` when write succeeds, else `false`.
1284
+ */
1285
+ async set(key, value, store = this.config.stores[0], options = this.config) {
1055
1286
  try {
1056
- const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).put(value, key);
1287
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).put(value, key);
1057
1288
  return new Promise((res) => req.onsuccess = () => res(true));
1058
1289
  } catch (e) {
1059
1290
  return this.warn("put", void 0, store), false;
1060
1291
  }
1061
1292
  }
1062
- async remove(key, store = this.config.stores[0]) {
1293
+ /**
1294
+ * Deletes a value by key from an object store.
1295
+ * @param key Record key.
1296
+ * @param store Optional object-store override.
1297
+ * @returns `true` when delete succeeds, else `false`.
1298
+ */
1299
+ async remove(key, store = this.config.stores[0], options = this.config) {
1063
1300
  try {
1064
- const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).delete(key);
1301
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).delete(key);
1065
1302
  return new Promise((res) => req.onsuccess = () => res(true));
1066
1303
  } catch (e) {
1067
1304
  return this.warn("delete", void 0, store), false;
1068
1305
  }
1069
1306
  }
1070
- async clear(stores = this.config.stores) {
1307
+ /**
1308
+ * Clears one or more object stores.
1309
+ * @param stores Store name or list of store names to clear.
1310
+ * @returns `true` when all clears succeed, else `false`.
1311
+ */
1312
+ async clear(stores = this.config.stores, options = this.config) {
1071
1313
  let success = true;
1072
1314
  for (const store of Array.isArray(stores) ? stores : [stores])
1073
1315
  try {
1074
- const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).clear();
1316
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).clear();
1075
1317
  await new Promise((res) => req.onsuccess = () => res(true));
1076
1318
  } catch (e) {
1077
1319
  this.warn("clear", void 0, store), success = false;
@@ -1079,6 +1321,7 @@ var IndexedDBAdapter = class extends AsyncStorageAdapter {
1079
1321
  return success;
1080
1322
  }
1081
1323
  };
1324
+ var COOKIE_ADAPTER_BUILD = { path: "/", sameSite: "Lax", domain: void 0, debug: false };
1082
1325
  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
1326
 
1084
1327
  // src/ts/utils/fn.ts
@@ -1091,93 +1334,122 @@ function setTimeout2(handler, timeout, ...args) {
1091
1334
  return sig.addEventListener("abort", kill, { once: true }), id;
1092
1335
  }
1093
1336
 
1094
- // src/ts/plugins/persist.ts
1095
- var PersistPlugin = class extends BaseReactorPlugin {
1096
- static plugName = "persist";
1337
+ // src/ts/modules/persist.ts
1338
+ var PersistModule = class extends BaseReactorModule {
1339
+ static moduleName = "persist";
1097
1340
  adapter;
1341
+ hydrateSeq = 0;
1098
1342
  saveTimeoutId = 0;
1099
1343
  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;
1344
+ let res = this.rtrs.size > 1 ? {} : void 0;
1345
+ for (const [rid, rtr] of this.rtrs) {
1346
+ 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;
1347
+ this.rtrs.size > 1 ? setAny(res, rid, val) : res = val;
1348
+ }
1349
+ return res;
1102
1350
  }
1103
1351
  constructor(config, rtr) {
1104
- super({ ...PERSIST_PLUGIN_BUILD, ...config }, rtr);
1352
+ super(mergeObjs(PERSIST_MODULE_BUILD, config), rtr, { hydrated: false });
1105
1353
  }
1106
1354
  wire() {
1107
1355
  "undefined" !== typeof window && window.addEventListener("pagehide", this.onDestroy, { signal: this.signal });
1108
1356
  "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 });
1357
+ this.config.on("adapter", this.handleAdapter, { signal: this.signal, immediate: true });
1358
+ this.config.on("disabled", this.handleDisabled, { signal: this.signal, immediate: true });
1359
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
1112
1360
  }
1113
- async handleAdapterChange({ value = LocalStorageAdapter }) {
1361
+ onAttach(rtr) {
1362
+ 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);
1363
+ }
1364
+ async handleAdapter({ value = LocalStorageAdapter }) {
1365
+ const seq = ++this.hydrateSeq;
1114
1366
  if (this.adapter && value === this.adapter.constructor) return;
1367
+ this.state.hydrated = false;
1115
1368
  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 });
1369
+ this.adapter = "function" === typeof value ? new value({ debug: !!this.rtrs.values().next().value?.canLog }) : value;
1370
+ try {
1371
+ let saved = this.adapter.get(this.config.key);
1372
+ const isAsync = saved instanceof Promise, { depth, merge = true } = parseEvtOpts(this.config.fanout ?? isAsync, fanoutOptsArr, "depth");
1373
+ saved = !isAsync ? saved : await saved;
1374
+ if (seq !== this.hydrateSeq || !saved) return;
1375
+ for (const [rid, rtr] of this.rtrs) {
1376
+ const entry = this.rtrs.size > 1 ? getAny(saved, rid) : saved;
1377
+ if (!entry) continue;
1378
+ const set = (p, news, olds) => (depth ? fanout : setAny)(rtr.core, p, merge ? mergeObjs(news, olds) : olds, depth ? { depth, crossRealms: rtr.config.crossRealms } : void 0);
1379
+ for (const p of this.config.paths ?? wpArr) set(p, getAny(rtr.core, p), getAny(entry, p));
1380
+ }
1381
+ for (const rtr of this.rtrs.values()) rtr.tick(depth ? "*" : this.config.paths ?? "*");
1382
+ } finally {
1383
+ if (seq === this.hydrateSeq) this.state.hydrated = true;
1384
+ }
1385
+ }
1386
+ handleDisabled({ value }) {
1387
+ for (const rtr of this.rtrs.values()) this.onAttach(rtr);
1126
1388
  value && this.adapter?.remove(this.config.key);
1127
1389
  }
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 });
1390
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
1391
+ for (const rtr of this.rtrs.values()) {
1392
+ for (const p of prevs) rtr.off(p, this.handleSave);
1393
+ for (const p of paths) rtr.off(p, this.handleSave), !this.config.disabled && rtr.on(p, this.handleSave, { signal: this.signal, immediate: true });
1394
+ }
1131
1395
  }
1132
- throttleSave() {
1396
+ handleSave(e) {
1397
+ if (!this.state.hydrated) return e.stopImmediatePropagation();
1133
1398
  if (!this.saveTimeoutId) this.saveTimeoutId = setTimeout2(() => (this.adapter.set(this.config.key, this.payload), this.saveTimeoutId = 0), this.config.throttle, this.signal);
1134
1399
  }
1135
- /** Clears persisted payload for this plugin instance and drops any pending save. */
1400
+ /** Clears persisted payload for this module instance and drops any pending save. */
1136
1401
  clear() {
1137
1402
  clearTimeout(this.saveTimeoutId);
1138
- this.saveTimeoutId = -1, this.rtr.stall(() => this.saveTimeoutId = 0);
1403
+ this.saveTimeoutId = -1;
1404
+ for (const rtr of this.rtrs.values()) rtr.stall(() => this.saveTimeoutId = 0);
1139
1405
  this.adapter?.remove(this.config.key);
1140
1406
  }
1141
1407
  onDestroy() {
1142
- !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
1408
+ this.state.hydrated && !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
1143
1409
  }
1144
1410
  };
1145
- var PERSIST_PLUGIN_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
1411
+ var PERSIST_MODULE_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
1146
1412
 
1147
1413
  // src/ts/utils/num.ts
1148
1414
  function clamp(min = 0, val, max = Infinity) {
1149
1415
  return Math.min(Math.max(val, min), max);
1150
1416
  }
1151
1417
 
1152
- // src/ts/plugins/timeTravel.ts
1153
- var TimeTravelPlugin = class extends BaseReactorPlugin {
1154
- static plugName = "timeTravel";
1418
+ // src/ts/modules/timeTravel.ts
1419
+ var TimeTravelModule = class extends BaseReactorModule {
1420
+ static moduleName = "timeTravel";
1155
1421
  lastTimestamp = 0;
1156
1422
  playbackTimeoutId = -1;
1157
1423
  constructor(config, rtr) {
1158
- super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { initialState: null, history: [], currentFrame: 0, paused: true });
1424
+ super({ ...TIME_TRAVEL_MODULE_BUILD, ...config }, rtr, { initialState: {}, history: [], currentFrame: 0, paused: true });
1159
1425
  }
1160
1426
  // ===========================================================================
1161
1427
  // THE FOUNDATION & WIRETAP (Passive Recording)
1162
1428
  // ===========================================================================
1163
1429
  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
1430
  this.lastTimestamp = performance.now();
1167
1431
  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 });
1432
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
1169
1433
  !this.state.paused && this.play();
1170
1434
  }
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 });
1435
+ onAttach(rtr, rid) {
1436
+ rtr.config.referenceTracking = rtr.config.smartCloning = rtr.config.eventTimeStamps = true;
1437
+ if (!this.state.history.length || !this.state.initialState[rid]) this.state.initialState[rid] = rtr.snapshot();
1438
+ for (const p of this.config.paths ?? wpArr) rtr.on(p, this.record, { signal: this.signal });
1439
+ }
1440
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
1441
+ for (const rtr of this.rtrs.values()) {
1442
+ for (const p of prevs) rtr.off(p, this.record);
1443
+ for (const p of paths) rtr.off(p, this.record), rtr.on(p, this.record, { signal: this.signal });
1444
+ }
1174
1445
  }
1175
1446
  /** Chronicling the lifecycle of the system, Captures the essence of every mutation wave that bubbles up. */
1176
- record(e) {
1447
+ record(e, rid = this.rids.get(e.reactor)) {
1177
1448
  if (!this.state.paused) return;
1178
- if (this.state.currentFrame < this.state.history.length) this.state.history = this.state.history.slice(0, this.state.currentFrame);
1179
- 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 });
1449
+ if (this.state.currentFrame < this.state.history.length) fanout(this.state, "history", this.state.history.slice(0, this.state.currentFrame), { atomic: true });
1450
+ if (this.state.history.length >= this.config.maxHistoryLength) fanout(this.state, "history", this.state.history.slice(1), { atomic: true });
1451
+ const en = { path: e.target.path, value: e.reactor.snapshot(false, e.target.value), oldValue: e.reactor.snapshot(false, e.target.oldValue), type: e.staticType, deltat: e.timestamp - this.lastTimestamp, rid };
1452
+ e.rejected && (en.rejected = e.rejected), !e.target.hadKey && (en.hadKey = false), this.state.history.push(en);
1181
1453
  this.state.currentFrame = this.state.history.length;
1182
1454
  this.lastTimestamp = e.timestamp;
1183
1455
  }
@@ -1186,7 +1458,7 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1186
1458
  this.pause();
1187
1459
  this.playbackTimeoutId = -1;
1188
1460
  this.state.history.length = this.state.currentFrame = 0;
1189
- this.state.initialState = this.rtr.snapshot();
1461
+ this.state.initialState = Object.fromEntries(this.rtrs.entries().map(([rid, rtr]) => [rid, rtr.snapshot()]));
1190
1462
  this.lastTimestamp = performance.now();
1191
1463
  }
1192
1464
  // ===========================================================================
@@ -1199,12 +1471,13 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1199
1471
  while (this.state.currentFrame !== target) {
1200
1472
  const e = this.state.history[forward ? this.state.currentFrame : this.state.currentFrame - 1];
1201
1473
  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));
1474
+ const rtr = this.rtrs.get(e.rid) || this.rtrs.values().next().value;
1475
+ if (forward) e.type === "delete" ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.value, rtr.config));
1476
+ else e.hadKey === false ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.oldValue, rtr.config));
1204
1477
  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}"`);
1478
+ if (e.rejected) rtr.log(`[Reactor ${this.name} Module] ${forward ? "Replaying" : "Reversing"} REJECTED intent at "${e.path}"`);
1206
1479
  }
1207
- this.rtr.tick();
1480
+ for (const rtr of this.rtrs.values()) rtr.tick();
1208
1481
  if (!keepShield) this.state.paused = true;
1209
1482
  }
1210
1483
  /** Step through time, Moves the playhead and teleports the state. */
@@ -1223,9 +1496,9 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1223
1496
  async automove(forward = true) {
1224
1497
  this.state.paused = false;
1225
1498
  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];
1499
+ const idx = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? idx + 1 : idx - 1];
1227
1500
  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));
1501
+ if (e?.deltat > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), Math.min(e.deltat, this.config.maxPlaybackDelay), this.signal));
1229
1502
  }
1230
1503
  this.state.paused = true;
1231
1504
  }
@@ -1240,38 +1513,34 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
1240
1513
  // ===========================================================================
1241
1514
  /** Exports the current session as a JSON string. */
1242
1515
  export(replacer, space) {
1243
- try {
1244
- return JSON.stringify(this.state, replacer, space);
1245
- } catch (e) {
1246
- return this.rtr.log(`[Reactor ${this.name} Plug] Failed to export session`), "";
1247
- }
1516
+ return JSON.stringify(this.state, replacer, space);
1248
1517
  }
1249
1518
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
1250
- import(json) {
1251
- try {
1252
- setAny(this.state, "*", JSON.parse(json));
1253
- this.lastTimestamp = performance.now();
1254
- const resume = !this.state.paused, target = this.state.currentFrame;
1255
- this.state.paused = false;
1256
- setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config)), this.rtr.tick();
1257
- this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
1258
- } catch (e) {
1259
- this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`);
1260
- }
1519
+ import(json, reviver) {
1520
+ setAny(this.state, "*", JSON.parse(json, reviver));
1521
+ this.lastTimestamp = performance.now();
1522
+ const resume = !this.state.paused, target = this.state.currentFrame;
1523
+ this.state.paused = false;
1524
+ for (const [rid, rtr] of this.rtrs) setAny(rtr.core, "*", deepClone(this.state.initialState[rid], rtr.config)), rtr.tick();
1525
+ this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
1261
1526
  }
1262
1527
  };
1263
- var TIME_TRAVEL_PLUGIN_BUILD = { maxPlaybackDelay: 2e3 };
1528
+ var TIME_TRAVEL_MODULE_BUILD = { maxPlaybackDelay: 2e3 };
1264
1529
  // Annotate the CommonJS export names for ESM import in node:
1265
1530
  0 && (module.exports = {
1266
- BaseReactorPlugin,
1531
+ AsyncStorageAdapter,
1532
+ BaseReactorModule,
1267
1533
  BaseStorageAdapter,
1534
+ COOKIE_ADAPTER_BUILD,
1535
+ CookieAdapter,
1268
1536
  INDEXED_DB_ADAPTER_BUILD,
1269
1537
  IndexedDBAdapter,
1270
1538
  LocalStorageAdapter,
1271
- MemoryStorageAdapter,
1272
- PERSIST_PLUGIN_BUILD,
1273
- PersistPlugin,
1539
+ MemoryAdapter,
1540
+ PERSIST_MODULE_BUILD,
1541
+ PersistModule,
1542
+ SessionStorageAdapter,
1274
1543
  StorageAdapter,
1275
- TIME_TRAVEL_PLUGIN_BUILD,
1276
- TimeTravelPlugin
1544
+ TIME_TRAVEL_MODULE_BUILD,
1545
+ TimeTravelModule
1277
1546
  });