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
@@ -23,7 +23,6 @@ var sia = (() => {
23
23
  __export(super_exports, {
24
24
  CTX: () => CTX,
25
25
  EVT_OPTS: () => EVT_OPTS,
26
- EVT_WARN: () => EVT_WARN,
27
26
  INDIFFABLE: () => INDIFFABLE,
28
27
  INERTIA: () => INERTIA,
29
28
  NIL: () => NIL,
@@ -48,7 +47,7 @@ var sia = (() => {
48
47
  isVolatile: () => isVolatile,
49
48
  live: () => live,
50
49
  methods: () => methods,
51
- plugins: () => plugins_exports,
50
+ modules: () => modules_exports,
52
51
  reactive: () => reactive,
53
52
  stable: () => stable,
54
53
  state: () => state,
@@ -60,7 +59,9 @@ var sia = (() => {
60
59
  var CTX = {
61
60
  /** Flag indicating whether the application is running in development mode. */
62
61
  isDevEnv: "undefined" !== typeof process ? process.env.NODE_ENV !== "production" : true,
63
- /** active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
62
+ /** Flag indicating whether a cascade is currently ongoing so reactors can allow all writes. */
63
+ isCascading: false,
64
+ /** Active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
64
65
  autotracker: null
65
66
  };
66
67
  var RAW = /* @__PURE__ */ Symbol.for("S.I.A_RAW");
@@ -72,14 +73,13 @@ var sia = (() => {
72
73
  var SSVERSION = /* @__PURE__ */ Symbol.for("S.I.A_SNAPSHOT_VERSION");
73
74
  var RTR_BATCH = "undefined" !== typeof window ? ("undefined" !== typeof queueMicrotask ? queueMicrotask : setTimeout).bind(window) : "undefined" !== typeof process && process.nextTick ? process.nextTick : setTimeout;
74
75
  var RTR_LOG = console.log.bind(console, "[S.I.A Reactor]");
75
- var EVT_WARN = console.warn.bind(console, "[S.I.A Event]");
76
76
  var EVT_OPTS = { LISTENER: ["capture", "depth", "once", "signal", "immediate"], MEDIATOR: ["lazy", "signal", "immediate"] };
77
77
  var NIL = Object.freeze({});
78
78
  var NOOP = () => {
79
79
  };
80
80
 
81
81
  // src/ts/utils/obj.ts
82
- var arrRx = /^([^\[\]]+)\[(\d+)\]$/;
82
+ var arrRegex = /^([^\[\]]+)\[(\d+)\]$/;
83
83
  function isObj(obj, arraycheck = true) {
84
84
  return "object" === typeof obj && obj !== null && (arraycheck ? !Array.isArray(obj) : true);
85
85
  }
@@ -87,7 +87,7 @@ var sia = (() => {
87
87
  return (typecheck ? isObj(obj, false) : true) && (config.crossRealms ? Object.prototype.toString.call(obj) === "[object Object]" : obj.constructor === Object);
88
88
  }
89
89
  function canHandle(obj, config = NIL, typecheck = true) {
90
- if (typecheck && !isObj(obj, false)) return false;
90
+ if (typecheck && !isObj(obj, false) || obj[INERTIA]) return false;
91
91
  if (Array.isArray(obj) || !config.preserveContext && isPOJO(obj, config, false)) return true;
92
92
  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);
93
93
  return false;
@@ -98,7 +98,7 @@ var sia = (() => {
98
98
  const keys2 = key.split(separator);
99
99
  let currObj = source;
100
100
  for (let i = 0, len = keys2.length; i < len; i++) {
101
- const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
101
+ const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRegex);
102
102
  if (match) {
103
103
  const [, key3, iStr] = match;
104
104
  if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return void 0;
@@ -115,7 +115,7 @@ var sia = (() => {
115
115
  if (!key.includes(separator)) return void (target[keyFunc ? keyFunc(key) : key] = value);
116
116
  const keys2 = key.split(separator);
117
117
  for (let currObj = target, i = 0, len = keys2.length; i < len; i++) {
118
- const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
118
+ const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRegex);
119
119
  if (match) {
120
120
  const [, key3, iStr] = match;
121
121
  if (!Array.isArray(currObj[key3])) currObj[key3] = [];
@@ -136,7 +136,7 @@ var sia = (() => {
136
136
  if (!key.includes(separator)) return void delete target[keyFunc ? keyFunc(key) : key];
137
137
  const keys2 = key.split(separator);
138
138
  for (let currObj = target, i = 0, len = keys2.length; i < len; i++) {
139
- const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
139
+ const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRegex);
140
140
  if (match) {
141
141
  const [, key3, iStr] = match;
142
142
  if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return;
@@ -154,7 +154,7 @@ var sia = (() => {
154
154
  if (!key.includes(separator)) return key in source;
155
155
  const keys2 = key.split(separator);
156
156
  for (let currObj = source, i = 0, len = keys2.length; i < len; i++) {
157
- const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
157
+ const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRegex);
158
158
  if (match) {
159
159
  const [, key3, iStr] = match;
160
160
  if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return false;
@@ -171,16 +171,48 @@ var sia = (() => {
171
171
  function parseAnyObj(obj, separator = ".", keyFunc = (p) => p, seen = /* @__PURE__ */ new WeakSet()) {
172
172
  if (!isObj(obj) || seen.has(obj)) return obj;
173
173
  seen.add(obj);
174
- const result = {};
175
- Object.keys(obj).forEach((k) => k === "*" || k.includes(separator) ? setAny(result, k, parseAnyObj(obj[k], separator, keyFunc, seen), separator, keyFunc) : result[k] = isObj(obj[k]) ? parseAnyObj(obj[k], separator, keyFunc, seen) : obj[k]);
174
+ const result = {}, keys2 = Object.keys(obj);
175
+ for (let i = 0, len = keys2.length; i < len; i++) {
176
+ const k = keys2[i];
177
+ k === "*" || k.includes(separator) ? setAny(result, k, parseAnyObj(obj[k], separator, keyFunc, seen), separator, keyFunc) : result[k] = isObj(obj[k]) ? parseAnyObj(obj[k], separator, keyFunc, seen) : obj[k];
178
+ }
176
179
  return result;
177
180
  }
178
181
  function parseEvtOpts(options, opts, boolOpt = opts[0], result = {}) {
179
182
  return Object.assign(result, "boolean" === typeof options ? { [boolOpt]: options } : options), result;
180
183
  }
181
- function mergeObjs(o1 = {}, o2 = {}) {
182
- const merged = { ...o1 || {}, ...o2 || {} };
183
- return Object.keys(merged).forEach((k) => isObj(o1?.[k]) && isObj(o2?.[k]) && (merged[k] = mergeObjs(o1[k], o2[k]))), merged;
184
+ function fanout(a, b, c, d) {
185
+ const isEvPd = !!a?.target, isPath = !isEvPd && "string" === typeof b, [state2, 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(state2, path) : olds;
186
+ if (isEvPd && type !== "set" && type !== "delete" || !target || !canHandle(news, opts)) return;
187
+ const prev = CTX.isCascading;
188
+ CTX.isCascading = isEvPd;
189
+ try {
190
+ const walk = (target2, obj, depth = isEvPd ? 1 : Infinity, keys2 = Object.keys(obj)) => {
191
+ for (let i = 0, len = keys2.length; i < len; i++) {
192
+ const val = obj[keys2[i]];
193
+ try {
194
+ if ((opts.atomic ?? true) && Array.isArray(val)) target2[keys2[i]] = val, target2[keys2[i]].length = target2[keys2[i]].length;
195
+ else depth > 1 && canHandle(val, opts) ? walk(target2[keys2[i]] ||= {}, val, depth - 1) : target2[keys2[i]] = val;
196
+ } catch (e) {
197
+ if (e instanceof RangeError) throw e;
198
+ }
199
+ }
200
+ };
201
+ if ((opts.atomic ?? true) && Array.isArray(news) && isPath) setAny(state2, path, news), getAny(state2, path).length = news.length;
202
+ else walk(target, opts.merge ? mergeObjs(olds, news, opts) : news, opts.depth === true ? Infinity : opts.depth);
203
+ } finally {
204
+ CTX.isCascading = prev;
205
+ }
206
+ }
207
+ var fanoutOptsArr = ["merge", "depth", "atomic"];
208
+ function mergeObjs(o1, o2, config, pojocheck = true) {
209
+ if (pojocheck && (!isPOJO(o1 || NIL, config) || !isPOJO(o2 || NIL, config))) return o2;
210
+ const merged = { ...o1 ||= {}, ...o2 ||= {} }, keys2 = Object.keys(merged);
211
+ for (let i = 0, len = keys2.length; i < len; i++) {
212
+ const o1C = o1[keys2[i]], o2C = o2[keys2[i]];
213
+ if (isPOJO(o1C, config) && isPOJO(o2C, config)) merged[keys2[i]] = mergeObjs(o1C, o2C, config, false);
214
+ }
215
+ return merged;
184
216
  }
185
217
  function getTrailRecords(obj, path, reverse = false) {
186
218
  const parts = path.split("."), chain = [["*", obj, obj]];
@@ -195,7 +227,12 @@ var sia = (() => {
195
227
  const clone = config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {};
196
228
  seen.set(obj, clone);
197
229
  const keys2 = config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
198
- for (let i = 0, len = keys2.length; i < len; i++) clone[keys2[i]] = deepClone(obj[keys2[i]], config, seen);
230
+ for (let i = 0, len = keys2.length; i < len; i++)
231
+ try {
232
+ clone[keys2[i]] = deepClone(obj[keys2[i]], config, seen);
233
+ } catch (e) {
234
+ if (e instanceof RangeError) throw e;
235
+ }
199
236
  return clone;
200
237
  }
201
238
  function nuke(target) {
@@ -235,35 +272,34 @@ var sia = (() => {
235
272
  staticType;
236
273
  /** Original event target context. */
237
274
  target;
238
- /** Root reactive object for this event wave. */
275
+ /** Root reactive object for this event instance wave. */
239
276
  root;
240
- /** Original target path for this event wave. */
277
+ /** Original target path for this event instance wave. */
241
278
  path;
242
279
  /** Current value at the event target path. */
243
280
  value;
244
281
  /** Previous value at the event target path. */
245
282
  oldValue;
246
- /** Whether resolve/reject intent semantics are allowed for this event. */
283
+ /** Whether resolve/reject intent semantics are allowed for this event instance. */
247
284
  rejectable;
248
- /** Whether this event wave can bubble back up to ancestors or just capture down. */
285
+ /** Whether this event instance wave can bubble back up to ancestors or just capture down. */
249
286
  bubbles;
250
287
  /**
251
- * `DOMHighResTimeStamp` for this event payload for native event parity and accuracy.
288
+ * `DOMHighResTimeStamp` for this event instance payload for native event parity and accuracy.
252
289
  * Enable `eventTimeStamps` option, then use this over custom timestamps in listeners for accuracy.
253
290
  * */
254
291
  timestamp;
255
- _warn = NOOP;
292
+ /** The `Reactor` instance that dispatched this event instance. */
293
+ reactor;
256
294
  _resolved = "";
257
295
  _rejected = "";
258
296
  _propagationStopped = false;
259
297
  _immediatePropagationStopped = false;
260
298
  /**
261
299
  * @param payload Source payload for this event instance.
262
- * @param bubbles Whether bubbling is enabled for this wave.
263
- * @param canWarn Whether warning output is enabled.
264
- * @param canStamp Whether timestamping is enabled.
300
+ * @param reactor The `Reactor` instance creating this event instance.
265
301
  */
266
- constructor(payload, bubbles = false, canStamp = false, canWarn = true) {
302
+ constructor(payload, reactor) {
267
303
  this.staticType = this.type = payload.type;
268
304
  this.target = payload.target;
269
305
  this.currentTarget = payload.currentTarget;
@@ -272,9 +308,9 @@ var sia = (() => {
272
308
  this.value = payload.target.value;
273
309
  this.oldValue = payload.target.oldValue;
274
310
  this.rejectable = payload.rejectable;
275
- this.bubbles = bubbles;
276
- if (canStamp) this.timestamp = performance.now();
277
- if (canWarn) this._warn = EVT_WARN;
311
+ this.bubbles = !!reactor.config.eventBubbling;
312
+ if (reactor.config.eventTimeStamps) this.timestamp = performance.now();
313
+ this.reactor = reactor;
278
314
  }
279
315
  /** Whether propagation has been stopped. */
280
316
  get propagationStopped() {
@@ -304,9 +340,9 @@ var sia = (() => {
304
340
  * @example e.resolve("API Load successful"); // message
305
341
  */
306
342
  resolve(message) {
307
- if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored resolve() call on a non-rejectable ${this.staticType} at "${this.path}"`);
308
- 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.`);
309
- if (this.rejectable) this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`;
343
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`resolve()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
344
+ 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.`);
345
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`}`);
310
346
  }
311
347
  /** Rejection reason for rejectable events. */
312
348
  get rejected() {
@@ -319,9 +355,9 @@ var sia = (() => {
319
355
  * @example e.resolve("User is not logged in"); // reason
320
356
  */
321
357
  reject(reason) {
322
- if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored reject() call on a non-rejectable ${this.staticType} at "${this.path}"`);
323
- 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.`);
324
- if (this.rejectable) this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`;
358
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`reject()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
359
+ 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.`);
360
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`}`);
325
361
  }
326
362
  /**
327
363
  * Returns event path values from target to root.
@@ -330,24 +366,22 @@ var sia = (() => {
330
366
  composedPath() {
331
367
  return getTrailRecords(this.root, this.path, true).map((r) => r[2]);
332
368
  }
333
- get canWarn() {
334
- return this._warn !== NOOP;
335
- }
336
369
  };
337
370
 
338
371
  // src/ts/core/reactor.ts
339
372
  var Reactor = class {
373
+ /** Logger function for this reactor instance, override if desired, `this.canLog = false` resets. */
340
374
  log = NOOP;
375
+ /** The core state object for this reactor instance. */
341
376
  core;
342
377
  // `?:`s | pay the ~800 byte price upfront for what u might never use
343
- plugins;
378
+ /** The modules being used by this reactor. */
379
+ modules;
380
+ /** Configuration options for this reactor instance. */
344
381
  config;
382
+ /** Whether this reactor instance is currently batching updates, a window view into the engine timing */
345
383
  isBatching = false;
346
384
  // Async Batching
347
- isCascading = false;
348
- // Setter Cascading
349
- isLogging = false;
350
- // keeping track so API getter doesn't slow down internal iterations in any way
351
385
  queue;
352
386
  // Tasks to run after flush
353
387
  batch;
@@ -380,7 +414,7 @@ var sia = (() => {
380
414
  if (this.config.referenceTracking && parent && key && !this.link(target, parent, key, false)) return target;
381
415
  const cached = this.proxyCache.get(target);
382
416
  if (cached) return cached;
383
- if (target[INERTIA] || !canHandle(target, this.config, false)) return target;
417
+ if (!canHandle(target, this.config, false)) return target;
384
418
  rejectable ||= target[REJECTABLE];
385
419
  indiffable ||= target[INDIFFABLE];
386
420
  const proxy = new Proxy(target, {
@@ -414,7 +448,7 @@ var sia = (() => {
414
448
  safeValue = value?.[RAW] || value;
415
449
  unchanged = this.config.equalityFunction(safeValue, safeOldValue);
416
450
  }
417
- if (!indiffable && unchanged && !this.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
451
+ if (!indiffable && unchanged && !CTX.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
418
452
  if (this.config.set) terminated = (value = this.config.set(object, key2, value, oldValue, receiver, paths)) === TERMINATOR;
419
453
  if (this.setters) {
420
454
  const wildcords = this.setters.get("*");
@@ -568,7 +602,7 @@ var sia = (() => {
568
602
  if (this.queue?.size) for (const task of this.queue) task(), this.queue.delete(task);
569
603
  }
570
604
  wave(path, payload) {
571
- const e = new ReactorEvent(payload, this.config.eventBubbling, this.config.eventTimeStamps, this.isLogging), chain = getTrailRecords(this.core, path);
605
+ const e = new ReactorEvent(payload, this), chain = getTrailRecords(this.core, path);
572
606
  e.eventPhase = ReactorEvent.CAPTURING_PHASE;
573
607
  for (let i = 0; i <= chain.length - 2; i++) {
574
608
  if (e.propagationStopped) break;
@@ -642,8 +676,8 @@ var sia = (() => {
642
676
  return depth;
643
677
  }
644
678
  getContext(path) {
645
- const lastDot = path.lastIndexOf("."), value = getAny(this.core, path), object = lastDot === -1 ? this.core : getAny(this.core, path.slice(0, lastDot));
646
- return { path, value, key: path.slice(lastDot + 1) || "", hadKey: true, object };
679
+ const last = path.lastIndexOf("."), value = getAny(this.core, path), object = last === -1 ? this.core : getAny(this.core, path.slice(0, last));
680
+ return { path, value, key: path.slice(last + 1) || "", hadKey: true, object };
647
681
  }
648
682
  bindSignal(cord, sig) {
649
683
  if (sig) sig.aborted ? cord.clup() : sig.addEventListener("abort", cord.clup, { once: true });
@@ -659,7 +693,12 @@ var sia = (() => {
659
693
  const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj;
660
694
  seen.set(obj, clone);
661
695
  const keys2 = this.config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
662
- for (let i = 0, len = keys2.length; i < len; i++) clone[keys2[i]] = this.cloned(obj[keys2[i]], raw, seen);
696
+ for (let i = 0, len = keys2.length; i < len; i++)
697
+ try {
698
+ clone[keys2[i]] = this.cloned(obj[keys2[i]], raw, seen);
699
+ } catch (e) {
700
+ if (e instanceof RangeError) throw e;
701
+ }
663
702
  if (!raw && this.config.smartCloning) this.snapCache.set(obj, clone), obj[SSVERSION] = version;
664
703
  return clone;
665
704
  }
@@ -747,7 +786,7 @@ var sia = (() => {
747
786
  * rtr.delete("cache.temp", () => TERMINATOR);
748
787
  */
749
788
  delete(path, callback, options) {
750
- return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path, void 0));
789
+ return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path));
751
790
  }
752
791
  /** Registers a delete mediator for a path that only triggers once. */
753
792
  donce(path, callback, options) {
@@ -764,7 +803,7 @@ var sia = (() => {
764
803
  }
765
804
  /**
766
805
  * Registers a watcher for a path.
767
- * Watch callbacks run synchronously with the operation.
806
+ * Watch callbacks run synchronously with the operation, use leaf paths for reliability as it sees exact sets; no bubbling here.
768
807
  * @param path Path or wildcard path.
769
808
  * @param callback Watch callback.
770
809
  * @param options Sync options.
@@ -813,7 +852,7 @@ var sia = (() => {
813
852
  cord = { cb: callback, capture, depth, once, clup: () => this.off(path, callback, options), lDepth: depth !== void 0 ? this.getDepth(path) : depth };
814
853
  if (immediate && (immediate !== "auto" || inAny(this.core, path))) {
815
854
  const target = this.getContext(path);
816
- callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this.config.eventBubbling, this.isLogging));
855
+ callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this));
817
856
  }
818
857
  (cords ?? (this.listeners.set(path, cords = []), cords)).push(cord);
819
858
  return this.bindSignal(cord, signal);
@@ -844,56 +883,39 @@ var sia = (() => {
844
883
  return this.cloned(arguments.length < 2 ? this.core : branch, raw);
845
884
  }
846
885
  /**
847
- * Cascades object updates into direct child paths.
848
- * @param payload Event or payload source.
849
- * @param objectSafe Merge old/new object values before cascading, don't set for arrays; merger doesn't play nice
850
- * @example
851
- * rtr.on("user", (event) => rtr.cascade(event));
852
- * @example
853
- * rtr.watch("user", (_value, payload) => rtr.cascade(payload));
854
- */
855
- cascade({ type, currentTarget: { path, value: news, oldValue: olds } }, objectSafe = true) {
856
- if (type !== "set" && type !== "delete" || !canHandle(news, this.config) || (objectSafe ? !canHandle(olds, this.config) : false)) return;
857
- const obj = objectSafe ? mergeObjs(olds, news) : news, keys2 = Object.keys(obj);
858
- this.isCascading = true;
859
- for (let i = 0, len = keys2.length; i < len; i++) setAny(this.core, path === "*" ? keys2[i] : path + "." + keys2[i], obj[keys2[i]]);
860
- this.isCascading = false;
861
- }
862
- /**
863
- * Installs a plugin instance.
864
- * @param plugin Plugin instance.
865
- * @returns Current reactor for fluent chaining.
886
+ * Installs a module instance.
887
+ * @param target Module instance.
888
+ * @param id Optional identification tag for this instance in the module.
889
+ * @returns Current `Reactor` instance for fluent chaining.
866
890
  */
867
- plugIn(plugin) {
868
- const name = plugin.constructor.plugName;
869
- this.plugins?.get(name)?.destroy();
870
- return (this.plugins ??= /* @__PURE__ */ new Map()).set(name, (plugin.setup(this), plugin)), this;
891
+ use(target, id) {
892
+ return (this.modules ??= /* @__PURE__ */ new Set()).add(target.setup(this, id)), this;
871
893
  }
872
- /** Resets the reactor to its initial state. */
894
+ /** Resets this reactor instance to its initial state. */
873
895
  reset() {
874
896
  this.getters?.clear(), this.setters?.clear(), this.deleters?.clear(), this.watchers?.clear(), this.listeners?.clear();
875
897
  this.batch?.clear(), this.queue?.clear(), this.isBatching = false;
876
898
  }
877
899
  destroy() {
878
- if (this.plugins) for (const plug of this.plugins.values()) plug.destroy();
900
+ if (this.modules) for (const mdle of this.modules) mdle.destroy();
879
901
  this.reset(), nuke(this);
880
902
  }
881
903
  get canLog() {
882
- return this.isLogging = this.log !== NOOP;
904
+ return this.log !== NOOP;
883
905
  }
884
906
  set canLog(value) {
885
- this.log = (this.isLogging = value) ? RTR_LOG : NOOP;
907
+ this.log = value ? RTR_LOG : NOOP;
886
908
  }
887
- get canTraceLineage() {
888
- return this.config.referenceTracking && !!this.config.lineageTracing;
909
+ get canLineageTrace() {
910
+ return this.config.lineageTracing && this.config.referenceTracking;
889
911
  }
890
912
  get canSmartClone() {
891
- return this.config.referenceTracking && !!this.config.smartCloning;
913
+ return this.config.smartCloning && this.config.referenceTracking;
892
914
  }
893
915
  };
894
916
 
895
917
  // src/ts/core/mixins.ts
896
- 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"];
918
+ var methods = ["tick", "stall", "nostall", "get", "gonce", "noget", "set", "sonce", "noset", "delete", "donce", "nodelete", "watch", "wonce", "nowatch", "on", "once", "off", "snapshot", "use", "reset", "destroy"];
897
919
  function reactive(target, build, preferences = NIL) {
898
920
  if ("__Reactor__" in target) return target;
899
921
  const descriptors = {}, rtr = getReactor(target, true, build), locks = { enumerable: false, configurable: true, writable: false }, hasAffix = !!(preferences.prefix || preferences.suffix);
@@ -901,7 +923,7 @@ var sia = (() => {
901
923
  let key = methods[i];
902
924
  if (hasAffix) (preferences.whitelist?.includes(key) ?? true) && (key = `${preferences.prefix || ""}${key}${preferences.suffix || ""}`);
903
925
  else if (preferences.whitelist?.includes(key)) continue;
904
- descriptors[key] = { value: rtr[key].bind(rtr), ...locks };
926
+ descriptors[key] = { value: rtr[methods[i]].bind(rtr), ...locks };
905
927
  }
906
928
  descriptors["__Reactor__"] = { value: rtr, ...locks };
907
929
  return Object.defineProperties(rtr.core, descriptors), rtr.core;
@@ -912,7 +934,7 @@ var sia = (() => {
912
934
  function live(target) {
913
935
  return delete getRaw(target)[INERTIA], target;
914
936
  }
915
- function isInert(target) {
937
+ function isInert(target = NIL) {
916
938
  return !!getRaw(target)[INERTIA];
917
939
  }
918
940
  function intent(target) {
@@ -921,7 +943,7 @@ var sia = (() => {
921
943
  function state(target) {
922
944
  return delete getRaw(target)[REJECTABLE], target;
923
945
  }
924
- function isIntent(target) {
946
+ function isIntent(target = NIL) {
925
947
  return !!getRaw(target)[REJECTABLE];
926
948
  }
927
949
  function volatile(target) {
@@ -930,25 +952,26 @@ var sia = (() => {
930
952
  function stable(target) {
931
953
  return delete getRaw(target)[INDIFFABLE], target;
932
954
  }
933
- function isVolatile(target) {
955
+ function isVolatile(target = NIL) {
934
956
  return !!getRaw(target)[INDIFFABLE];
935
957
  }
936
958
  function getReactor(target, create = false, build) {
937
959
  return (target instanceof Reactor ? target : target.__Reactor__) || (create ? new Reactor(target, build) : void 0);
938
960
  }
939
- function getRaw(target) {
940
- return target?.[RAW] || target;
961
+ function getRaw(target = NIL) {
962
+ return target[RAW] || target;
941
963
  }
942
- function getVersion(target) {
964
+ function getVersion(target = NIL) {
943
965
  return getRaw(target)[VERSION] || 0;
944
966
  }
945
- function getSnapshotVersion(target) {
967
+ function getSnapshotVersion(target = NIL) {
946
968
  return getRaw(target)[SSVERSION] || 0;
947
969
  }
948
970
 
949
971
  // src/ts/utils.ts
950
972
  var utils_exports = {};
951
973
  __export(utils_exports, {
974
+ arrRegex: () => arrRegex,
952
975
  assignEl: () => assignEl,
953
976
  bindAllMethods: () => bindAllMethods,
954
977
  canHandle: () => canHandle,
@@ -957,6 +980,8 @@ var sia = (() => {
957
980
  createEl: () => createEl,
958
981
  deepClone: () => deepClone,
959
982
  deleteAny: () => deleteAny,
983
+ fanout: () => fanout,
984
+ fanoutOptsArr: () => fanoutOptsArr,
960
985
  formatKeyForDisplay: () => formatKeyForDisplay,
961
986
  formatKeyShortcutsForDisplay: () => formatKeyShortcutsForDisplay,
962
987
  getAny: () => getAny,
@@ -1123,63 +1148,95 @@ var sia = (() => {
1123
1148
  }
1124
1149
  }
1125
1150
 
1126
- // src/ts/plugins.ts
1127
- var plugins_exports = {};
1128
- __export(plugins_exports, {
1129
- BaseReactorPlugin: () => BaseReactorPlugin,
1151
+ // src/ts/modules.ts
1152
+ var modules_exports = {};
1153
+ __export(modules_exports, {
1154
+ AsyncStorageAdapter: () => AsyncStorageAdapter,
1155
+ BaseReactorModule: () => BaseReactorModule,
1130
1156
  BaseStorageAdapter: () => BaseStorageAdapter,
1157
+ COOKIE_ADAPTER_BUILD: () => COOKIE_ADAPTER_BUILD,
1158
+ CookieAdapter: () => CookieAdapter,
1131
1159
  INDEXED_DB_ADAPTER_BUILD: () => INDEXED_DB_ADAPTER_BUILD,
1132
1160
  IndexedDBAdapter: () => IndexedDBAdapter,
1133
1161
  LocalStorageAdapter: () => LocalStorageAdapter,
1134
- MemoryStorageAdapter: () => MemoryStorageAdapter,
1135
- PERSIST_PLUGIN_BUILD: () => PERSIST_PLUGIN_BUILD,
1136
- PersistPlugin: () => PersistPlugin,
1162
+ MemoryAdapter: () => MemoryAdapter,
1163
+ PERSIST_MODULE_BUILD: () => PERSIST_MODULE_BUILD,
1164
+ PersistModule: () => PersistModule,
1165
+ SessionStorageAdapter: () => SessionStorageAdapter,
1137
1166
  StorageAdapter: () => StorageAdapter,
1138
- TIME_TRAVEL_PLUGIN_BUILD: () => TIME_TRAVEL_PLUGIN_BUILD,
1139
- TimeTravelPlugin: () => TimeTravelPlugin
1167
+ TIME_TRAVEL_MODULE_BUILD: () => TIME_TRAVEL_MODULE_BUILD,
1168
+ TimeTravelModule: () => TimeTravelModule
1140
1169
  });
1141
1170
 
1142
- // src/ts/plugins/base.ts
1143
- var BaseReactorPlugin = class {
1144
- static plugName;
1171
+ // src/ts/modules/base.ts
1172
+ var wpArr = ["*"];
1173
+ var BaseReactorModule = class {
1174
+ static moduleName;
1145
1175
  get name() {
1146
- return this.constructor.plugName;
1176
+ return this.constructor.moduleName;
1147
1177
  }
1148
1178
  ac = new AbortController();
1149
1179
  signal = this.ac.signal;
1150
- rtr;
1180
+ rtrs = /* @__PURE__ */ new Map();
1181
+ rids = /* @__PURE__ */ new WeakMap();
1182
+ // for quick 0(1) lookups over iteration
1183
+ wired = false;
1151
1184
  config;
1152
1185
  state;
1153
1186
  constructor(config, rtr, state2) {
1154
1187
  guardAllMethods(this, this.guard);
1155
- this.rtr = rtr;
1156
1188
  this.config = isObj(config) ? reactive(config) : config;
1157
1189
  this.state = isObj(state2) ? reactive(state2) : state2;
1190
+ rtr && this.attach(rtr);
1158
1191
  }
1159
- /** Entry point called to initialize plugin wiring. */
1160
- setup(rtr) {
1161
- this.rtr ??= rtr;
1162
- this.wire();
1192
+ /**
1193
+ * Connect to a `Reactor` instance, allows managing multiple reactors if needed.
1194
+ * @param target `Reactor` instance or `reactive()` object to connect to.
1195
+ * @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.
1196
+ * @returns Current `ReactorModule` instance for fluent chaining.
1197
+ * @example
1198
+ * const mod = new MyModule().attach(state1).attach(state2); // implicit index-based ids by default, add a .setup() or `Reactor.use()` when ready for init.
1199
+ * @example
1200
+ * const persist = new PersistModule(config).attach(sessState, "session").attach(adminState, "session.admin"); // don't use "*", causes de-serialization issues.
1201
+ */
1202
+ attach(target, id = this.rtrs.size) {
1203
+ const rtr = getReactor(target);
1204
+ if (!rtr || this.rtrs.has(id)) return this;
1205
+ return this.rids.set((this.rtrs.set(id, rtr), rtr), id), this.onAttach(rtr, id), this;
1206
+ }
1207
+ onAttach(_rtr, _rid) {
1208
+ }
1209
+ /**
1210
+ * Entry point called to initialize module wiring, calls `.attach(target, id)` first, `Reactor.use()` calls this internally.
1211
+ * Should run as last in `.attach()` chain or after all desired reactors if using multiple; so wiring is done safely after.
1212
+ * @param target `Reactor` instance or `reactive()` object to connect to.
1213
+ * @param id Optional id for the reactor, prefer over default implicit index id when managing multiple reactors.
1214
+ * @returns Current `ReactorModule` instance for fluent chaining.
1215
+ * @example
1216
+ * const mod = new MyModule().attach(state1).setup(state2); // if using multiple, this should run last; with same params as `.attach()` for a shorter chain
1217
+ */
1218
+ setup(target, id) {
1219
+ return this.attach(target, id), !this.wired && (this.wire(), this.wired = true), this;
1163
1220
  }
1164
1221
  destroy() {
1165
1222
  this.ac.abort();
1166
1223
  this.onDestroy?.();
1167
1224
  }
1168
1225
  /**
1169
- * Wraps a function with plugin-scoped error logging.
1226
+ * Wraps a function with module-scoped error logging.
1170
1227
  * Use this when creating functions dynamically (for example, before attaching an anonymous listener on the fly).
1171
1228
  * @example
1172
1229
  * window.addEventListener("resize", this.guard(() => this.syncLayout(true)), { signal: this.signal });
1173
1230
  */
1174
1231
  guard = (fn) => {
1175
- return guardMethod(fn, (e) => this.rtr.log(`[Reactor "${this.name}" Plug] Error: ${e}`));
1232
+ return guardMethod(fn, (e) => this.rtrs.values().next().value?.log(`[Reactor "${this.name}" Module] Error: ${e}`));
1176
1233
  };
1177
1234
  // `()=>{}`: needs to be bounded even before initialization
1178
1235
  };
1179
1236
 
1180
1237
  // src/ts/utils/store.ts
1181
1238
  var BaseStorageAdapter = class {
1182
- static name;
1239
+ name = "StorageAdapter";
1183
1240
  config;
1184
1241
  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})`);
1185
1242
  constructor(config) {
@@ -1187,26 +1244,44 @@ var sia = (() => {
1187
1244
  }
1188
1245
  };
1189
1246
  var StorageAdapter = class extends BaseStorageAdapter {
1247
+ name = "SyncStorageAdapter";
1190
1248
  };
1191
1249
  var AsyncStorageAdapter = class extends BaseStorageAdapter {
1250
+ name = "AsyncStorageAdapter";
1192
1251
  };
1193
1252
  var LocalStorageAdapter = class extends StorageAdapter {
1194
- static name = "LocalStorage";
1195
- get(key) {
1253
+ name = "LocalStorage";
1254
+ /**
1255
+ * Reads and parses a value from localStorage.
1256
+ * @param key Storage key.
1257
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1258
+ */
1259
+ get(key, reviver = this.config.reviver) {
1196
1260
  try {
1197
1261
  const v = localStorage.getItem(key);
1198
- return v ? JSON.parse(v) : void 0;
1262
+ return v ? JSON.parse(v, reviver) : void 0;
1199
1263
  } catch {
1200
1264
  return void 0;
1201
1265
  }
1202
1266
  }
1203
- set(key, value) {
1267
+ /**
1268
+ * Serializes and writes a value to localStorage.
1269
+ * @param key Storage key.
1270
+ * @param value Value to serialize.
1271
+ * @returns `true` when write succeeds, else `false`.
1272
+ */
1273
+ set(key, value, replacer = this.config.replacer) {
1204
1274
  try {
1205
- return localStorage.setItem(key, JSON.stringify(value)), true;
1275
+ return localStorage.setItem(key, JSON.stringify(value, replacer)), true;
1206
1276
  } catch (e) {
1207
1277
  return this.warn("setItem", void 0, key), false;
1208
1278
  }
1209
1279
  }
1280
+ /**
1281
+ * Removes a single key from localStorage.
1282
+ * @param key Storage key.
1283
+ * @returns `true` when removal succeeds, else `false`.
1284
+ */
1210
1285
  remove(key) {
1211
1286
  try {
1212
1287
  return localStorage.removeItem(key), true;
@@ -1214,6 +1289,10 @@ var sia = (() => {
1214
1289
  return this.warn("removeItem", void 0, key), false;
1215
1290
  }
1216
1291
  }
1292
+ /**
1293
+ * Clears all localStorage entries for the current origin.
1294
+ * @returns `true` when clear succeeds, else `false`.
1295
+ */
1217
1296
  clear() {
1218
1297
  try {
1219
1298
  return localStorage.clear(), true;
@@ -1222,25 +1301,94 @@ var sia = (() => {
1222
1301
  }
1223
1302
  }
1224
1303
  };
1225
- var MemoryStorageAdapter = class extends StorageAdapter {
1304
+ var SessionStorageAdapter = class extends StorageAdapter {
1305
+ name = "SessionStorage";
1306
+ /**
1307
+ * Reads and parses a value from sessionStorage.
1308
+ * @param key Storage key.
1309
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1310
+ */
1311
+ get(key, reviver = this.config.reviver) {
1312
+ try {
1313
+ const v = sessionStorage.getItem(key);
1314
+ return v ? JSON.parse(v, reviver) : void 0;
1315
+ } catch {
1316
+ return void 0;
1317
+ }
1318
+ }
1319
+ /**
1320
+ * Serializes and writes a value to sessionStorage.
1321
+ * @param key Storage key.
1322
+ * @param value Value to serialize.
1323
+ * @returns `true` when write succeeds, else `false`.
1324
+ */
1325
+ set(key, value, replacer = this.config.replacer) {
1326
+ try {
1327
+ return sessionStorage.setItem(key, JSON.stringify(value, replacer)), true;
1328
+ } catch (e) {
1329
+ return this.warn("setItem", void 0, key), false;
1330
+ }
1331
+ }
1332
+ /**
1333
+ * Removes a single key from sessionStorage.
1334
+ * @param key Storage key.
1335
+ * @returns `true` when removal succeeds, else `false`.
1336
+ */
1337
+ remove(key) {
1338
+ try {
1339
+ return sessionStorage.removeItem(key), true;
1340
+ } catch (e) {
1341
+ return this.warn("removeItem", void 0, key), false;
1342
+ }
1343
+ }
1344
+ /**
1345
+ * Clears all sessionStorage entries for the current tab session.
1346
+ * @returns `true` when clear succeeds, else `false`.
1347
+ */
1348
+ clear() {
1349
+ try {
1350
+ return sessionStorage.clear(), true;
1351
+ } catch (e) {
1352
+ return this.warn("clear", void 0), false;
1353
+ }
1354
+ }
1355
+ };
1356
+ var MemoryAdapter = class extends StorageAdapter {
1357
+ name = "Memory";
1226
1358
  constructor(build) {
1227
1359
  super({ store: /* @__PURE__ */ new Map(), ...build });
1228
1360
  }
1229
- get(key) {
1361
+ /**
1362
+ * Reads and parses a value from memory storage.
1363
+ * @param key Storage key.
1364
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1365
+ */
1366
+ get(key, reviver = this.config.reviver) {
1230
1367
  try {
1231
1368
  const v = this.config.store.get(key);
1232
- return v ? JSON.parse(v) : void 0;
1369
+ return v ? JSON.parse(v, reviver) : void 0;
1233
1370
  } catch {
1234
1371
  return void 0;
1235
1372
  }
1236
1373
  }
1237
- set(key, value) {
1374
+ /**
1375
+ * Serializes and writes a value to memory storage.
1376
+ * @param key Storage key.
1377
+ * @param value Value to serialize.
1378
+ * @returns `true` when write succeeds, else `false`.
1379
+ */
1380
+ set(key, value, replacer = this.config.replacer) {
1238
1381
  try {
1239
- return this.config.store.set(key, JSON.stringify(value)), true;
1382
+ return this.config.store.set(key, JSON.stringify(value, replacer)), true;
1240
1383
  } catch (e) {
1241
1384
  return this.warn("set", void 0, key), false;
1242
1385
  }
1243
1386
  }
1387
+ /**
1388
+ * Removes a single key from memory storage.
1389
+ * @param key Storage key.
1390
+ * @returns `true` when removal succeeds, else `false`.
1391
+ */
1244
1392
  remove(key) {
1245
1393
  try {
1246
1394
  return this.config.store.delete(key), true;
@@ -1248,6 +1396,10 @@ var sia = (() => {
1248
1396
  return this.warn("remove", void 0, key), false;
1249
1397
  }
1250
1398
  }
1399
+ /**
1400
+ * Clears all entries from memory storage.
1401
+ * @returns `true` when clear succeeds, else `false`.
1402
+ */
1251
1403
  clear() {
1252
1404
  try {
1253
1405
  return this.config.store.clear(), true;
@@ -1256,52 +1408,147 @@ var sia = (() => {
1256
1408
  }
1257
1409
  }
1258
1410
  };
1411
+ var CookieAdapter = class extends StorageAdapter {
1412
+ name = "Cookie";
1413
+ 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}` : ""}`;
1414
+ constructor(build) {
1415
+ super({ secure: "undefined" !== typeof window && location.protocol === "https:", ...COOKIE_ADAPTER_BUILD, ...build });
1416
+ }
1417
+ /**
1418
+ * Reads and parses a cookie visible to the current page scope.
1419
+ * @param key Cookie key.
1420
+ * @returns Parsed value, or `undefined` when missing/unreadable.
1421
+ */
1422
+ get(key, reviver = this.config.reviver) {
1423
+ try {
1424
+ const k = encodeURIComponent(key) + "=";
1425
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
1426
+ if (!pair.startsWith(k)) continue;
1427
+ return JSON.parse(decodeURIComponent(pair.slice(k.length)), reviver);
1428
+ }
1429
+ return void 0;
1430
+ } catch {
1431
+ return void 0;
1432
+ }
1433
+ }
1434
+ /**
1435
+ * Writes a cookie with optional per-call scope/lifetime overrides.
1436
+ * @param key Cookie key.
1437
+ * @param value Value to serialize.
1438
+ * @param opts Optional per-call cookie options.
1439
+ * @returns `true` when write succeeds, else `false`.
1440
+ */
1441
+ set(key, value, opts, replacer = this.config.replacer) {
1442
+ try {
1443
+ return document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(value, replacer))}; ${this.deets(opts)}`, true;
1444
+ } catch {
1445
+ return this.warn("set", void 0, key), false;
1446
+ }
1447
+ }
1448
+ /**
1449
+ * Removes a cookie key using matching scope attributes.
1450
+ * @param key Cookie key.
1451
+ * @param opts Optional per-call scope overrides.
1452
+ * @returns `true` when removal succeeds, else `false`.
1453
+ */
1454
+ remove(key, opts) {
1455
+ try {
1456
+ return document.cookie = `${encodeURIComponent(key)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`, true;
1457
+ } catch {
1458
+ return this.warn("remove", void 0, key), false;
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Attempts to remove all visible cookie keys for the given scope.
1463
+ * @param opts Optional per-call scope overrides.
1464
+ * @returns `true` when clear succeeds, else `false`.
1465
+ */
1466
+ clear(opts) {
1467
+ try {
1468
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
1469
+ const idx = pair.indexOf("=");
1470
+ document.cookie = `${idx === -1 ? pair : pair.slice(0, idx)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`;
1471
+ }
1472
+ return true;
1473
+ } catch {
1474
+ return this.warn("clear"), false;
1475
+ }
1476
+ }
1477
+ };
1259
1478
  var IndexedDBAdapter = class extends AsyncStorageAdapter {
1479
+ name = "IndexedDB";
1260
1480
  db;
1261
- 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})`);
1262
1481
  constructor(build) {
1263
1482
  super({ ...INDEXED_DB_ADAPTER_BUILD, ...build });
1264
1483
  }
1484
+ /**
1485
+ * Returns a connected IndexedDB instance, opening it when needed.
1486
+ * @returns Connected database handle.
1487
+ */
1265
1488
  async idb() {
1266
1489
  const idb = this.config.onidb();
1267
1490
  if (idb || this.db) return Promise.resolve(idb || this.db);
1268
1491
  return new Promise((res, rej) => {
1269
1492
  const req = indexedDB.open(this.config.dbName, this.config.version);
1270
1493
  req.onupgradeneeded = (e) => (this.config.onupgradeneeded(req.result, e), this.config.stores.forEach((s) => !req.result.objectStoreNames.contains(s) && req.result.createObjectStore(s)));
1271
- 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));
1494
+ 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));
1272
1495
  req.onerror = (e) => (this.config.onerror(req.error, e), this.warn("open", "Something went wrong"), rej(req.error));
1273
1496
  req.onblocked = (e) => (this.config.onblocked(e), this.warn("open", "Close other tabs for updates"));
1274
1497
  });
1275
1498
  }
1276
- async get(key, store = this.config.stores[0]) {
1499
+ /**
1500
+ * Reads a value by key from an object store.
1501
+ * @param key Record key.
1502
+ * @param store Optional object-store override.
1503
+ * @returns Stored value, or `undefined` when missing/unreadable.
1504
+ */
1505
+ async get(key, store = this.config.stores[0], options = this.config) {
1277
1506
  try {
1278
- const req = (await this.idb()).transaction(store).objectStore(store).get(key);
1507
+ const req = (await this.idb()).transaction(store, "readonly", options).objectStore(store).get(key);
1279
1508
  return new Promise((res) => req.onsuccess = () => res(req.result));
1280
1509
  } catch {
1281
1510
  return this.warn("get", void 0, store), void 0;
1282
1511
  }
1283
1512
  }
1284
- async set(key, value, store = this.config.stores[0]) {
1513
+ /**
1514
+ * Writes a value by key into an object store.
1515
+ * @param key Record key.
1516
+ * @param value Value to store.
1517
+ * @param store Optional object-store override.
1518
+ * @returns `true` when write succeeds, else `false`.
1519
+ */
1520
+ async set(key, value, store = this.config.stores[0], options = this.config) {
1285
1521
  try {
1286
- const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).put(value, key);
1522
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).put(value, key);
1287
1523
  return new Promise((res) => req.onsuccess = () => res(true));
1288
1524
  } catch (e) {
1289
1525
  return this.warn("put", void 0, store), false;
1290
1526
  }
1291
1527
  }
1292
- async remove(key, store = this.config.stores[0]) {
1528
+ /**
1529
+ * Deletes a value by key from an object store.
1530
+ * @param key Record key.
1531
+ * @param store Optional object-store override.
1532
+ * @returns `true` when delete succeeds, else `false`.
1533
+ */
1534
+ async remove(key, store = this.config.stores[0], options = this.config) {
1293
1535
  try {
1294
- const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).delete(key);
1536
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).delete(key);
1295
1537
  return new Promise((res) => req.onsuccess = () => res(true));
1296
1538
  } catch (e) {
1297
1539
  return this.warn("delete", void 0, store), false;
1298
1540
  }
1299
1541
  }
1300
- async clear(stores = this.config.stores) {
1542
+ /**
1543
+ * Clears one or more object stores.
1544
+ * @param stores Store name or list of store names to clear.
1545
+ * @returns `true` when all clears succeed, else `false`.
1546
+ */
1547
+ async clear(stores = this.config.stores, options = this.config) {
1301
1548
  let success = true;
1302
1549
  for (const store of Array.isArray(stores) ? stores : [stores])
1303
1550
  try {
1304
- const req = (await this.idb()).transaction(store, "readwrite").objectStore(store).clear();
1551
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).clear();
1305
1552
  await new Promise((res) => req.onsuccess = () => res(true));
1306
1553
  } catch (e) {
1307
1554
  this.warn("clear", void 0, store), success = false;
@@ -1309,90 +1556,120 @@ var sia = (() => {
1309
1556
  return success;
1310
1557
  }
1311
1558
  };
1559
+ var COOKIE_ADAPTER_BUILD = { path: "/", sameSite: "Lax", domain: void 0, debug: false };
1312
1560
  var INDEXED_DB_ADAPTER_BUILD = { dbName: "REACTOR_IDB", stores: ["VAULT"], version: 1, onidb: NOOP, onupgradeneeded: NOOP, onversionchange: NOOP, onsuccess: NOOP, onerror: NOOP, onblocked: NOOP };
1313
1561
 
1314
- // src/ts/plugins/persist.ts
1315
- var PersistPlugin = class extends BaseReactorPlugin {
1316
- static plugName = "persist";
1562
+ // src/ts/modules/persist.ts
1563
+ var PersistModule = class extends BaseReactorModule {
1564
+ static moduleName = "persist";
1317
1565
  adapter;
1566
+ hydrateSeq = 0;
1318
1567
  saveTimeoutId = 0;
1319
1568
  get payload() {
1320
- const snap = this.config.useSnapshot ? this.rtr.snapshot() : this.rtr.core;
1321
- return this.config.paths ? this.config.paths.reduce((acc, p) => (setAny(acc, p, getAny(snap, p)), acc), {}) : snap;
1569
+ let res = this.rtrs.size > 1 ? {} : void 0;
1570
+ for (const [rid, rtr] of this.rtrs) {
1571
+ 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;
1572
+ this.rtrs.size > 1 ? setAny(res, rid, val) : res = val;
1573
+ }
1574
+ return res;
1322
1575
  }
1323
1576
  constructor(config, rtr) {
1324
- super({ ...PERSIST_PLUGIN_BUILD, ...config }, rtr);
1577
+ super(mergeObjs(PERSIST_MODULE_BUILD, config), rtr, { hydrated: false });
1325
1578
  }
1326
1579
  wire() {
1327
1580
  "undefined" !== typeof window && window.addEventListener("pagehide", this.onDestroy, { signal: this.signal });
1328
1581
  "undefined" !== typeof document && document.addEventListener("visibilitychange", () => document.visibilityState === "hidden" && this.onDestroy(), { signal: this.signal });
1329
- this.config.on("adapter", this.handleAdapterChange, { signal: this.signal, immediate: true });
1330
- this.config.on("disabled", this.handleDisabledChange, { signal: this.signal, immediate: true });
1331
- this.config.on("paths", this.handlePathsState, { signal: this.signal, immediate: true });
1582
+ this.config.on("adapter", this.handleAdapter, { signal: this.signal, immediate: true });
1583
+ this.config.on("disabled", this.handleDisabled, { signal: this.signal, immediate: true });
1584
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
1585
+ }
1586
+ onAttach(rtr) {
1587
+ 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);
1332
1588
  }
1333
- async handleAdapterChange({ value = LocalStorageAdapter }) {
1589
+ async handleAdapter({ value = LocalStorageAdapter }) {
1590
+ const seq = ++this.hydrateSeq;
1334
1591
  if (this.adapter && value === this.adapter.constructor) return;
1592
+ this.state.hydrated = false;
1335
1593
  this.adapter?.remove(this.config.key);
1336
- this.adapter = "function" === typeof value ? new value({ debug: this.rtr.canLog }) : value;
1337
- let saved = this.adapter.get(this.config.key);
1338
- const isAsync = saved instanceof Promise;
1339
- saved = !isAsync ? saved : await saved;
1340
- const set = (p, news, olds) => setAny(this.rtr.core, p, isPOJO(news, this.rtr.config) && isPOJO(olds, this.rtr.config) ? mergeObjs(news, olds) : olds);
1341
- if (saved) for (const p of this.config.paths ?? ["*"]) set(p, getAny(this.rtr.core, p), getAny(saved, p));
1342
- saved && this.rtr.tick(this.config.paths ?? "*");
1343
- }
1344
- handleDisabledChange({ value }) {
1345
- 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 });
1594
+ this.adapter = "function" === typeof value ? new value({ debug: !!this.rtrs.values().next().value?.canLog }) : value;
1595
+ try {
1596
+ let saved = this.adapter.get(this.config.key);
1597
+ const isAsync = saved instanceof Promise, { depth, merge = true } = parseEvtOpts(this.config.fanout ?? isAsync, fanoutOptsArr, "depth");
1598
+ saved = !isAsync ? saved : await saved;
1599
+ if (seq !== this.hydrateSeq || !saved) return;
1600
+ for (const [rid, rtr] of this.rtrs) {
1601
+ const entry = this.rtrs.size > 1 ? getAny(saved, rid) : saved;
1602
+ if (!entry) continue;
1603
+ const set = (p, news, olds) => (depth ? fanout : setAny)(rtr.core, p, merge ? mergeObjs(news, olds) : olds, depth ? { depth, crossRealms: rtr.config.crossRealms } : void 0);
1604
+ for (const p of this.config.paths ?? wpArr) set(p, getAny(rtr.core, p), getAny(entry, p));
1605
+ }
1606
+ for (const rtr of this.rtrs.values()) rtr.tick(depth ? "*" : this.config.paths ?? "*");
1607
+ } finally {
1608
+ if (seq === this.hydrateSeq) this.state.hydrated = true;
1609
+ }
1610
+ }
1611
+ handleDisabled({ value }) {
1612
+ for (const rtr of this.rtrs.values()) this.onAttach(rtr);
1346
1613
  value && this.adapter?.remove(this.config.key);
1347
1614
  }
1348
- handlePathsState({ value: paths = ["*"], oldValue: prevs = ["*"] }) {
1349
- for (const p of prevs) this.rtr.off(p, this.throttleSave);
1350
- 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 });
1615
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
1616
+ for (const rtr of this.rtrs.values()) {
1617
+ for (const p of prevs) rtr.off(p, this.handleSave);
1618
+ for (const p of paths) rtr.off(p, this.handleSave), !this.config.disabled && rtr.on(p, this.handleSave, { signal: this.signal, immediate: true });
1619
+ }
1351
1620
  }
1352
- throttleSave() {
1621
+ handleSave(e) {
1622
+ if (!this.state.hydrated) return e.stopImmediatePropagation();
1353
1623
  if (!this.saveTimeoutId) this.saveTimeoutId = setTimeout2(() => (this.adapter.set(this.config.key, this.payload), this.saveTimeoutId = 0), this.config.throttle, this.signal);
1354
1624
  }
1355
- /** Clears persisted payload for this plugin instance and drops any pending save. */
1625
+ /** Clears persisted payload for this module instance and drops any pending save. */
1356
1626
  clear() {
1357
1627
  clearTimeout(this.saveTimeoutId);
1358
- this.saveTimeoutId = -1, this.rtr.stall(() => this.saveTimeoutId = 0);
1628
+ this.saveTimeoutId = -1;
1629
+ for (const rtr of this.rtrs.values()) rtr.stall(() => this.saveTimeoutId = 0);
1359
1630
  this.adapter?.remove(this.config.key);
1360
1631
  }
1361
1632
  onDestroy() {
1362
- !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
1633
+ this.state.hydrated && !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
1363
1634
  }
1364
1635
  };
1365
- var PERSIST_PLUGIN_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
1636
+ var PERSIST_MODULE_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
1366
1637
 
1367
- // src/ts/plugins/timeTravel.ts
1368
- var TimeTravelPlugin = class extends BaseReactorPlugin {
1369
- static plugName = "timeTravel";
1638
+ // src/ts/modules/timeTravel.ts
1639
+ var TimeTravelModule = class extends BaseReactorModule {
1640
+ static moduleName = "timeTravel";
1370
1641
  lastTimestamp = 0;
1371
1642
  playbackTimeoutId = -1;
1372
1643
  constructor(config, rtr) {
1373
- super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { initialState: null, history: [], currentFrame: 0, paused: true });
1644
+ super({ ...TIME_TRAVEL_MODULE_BUILD, ...config }, rtr, { initialState: {}, history: [], currentFrame: 0, paused: true });
1374
1645
  }
1375
1646
  // ===========================================================================
1376
1647
  // THE FOUNDATION & WIRETAP (Passive Recording)
1377
1648
  // ===========================================================================
1378
1649
  wire() {
1379
- this.rtr.config.referenceTracking = this.rtr.config.smartCloning = this.rtr.config.eventTimeStamps = true;
1380
- if (!this.state.history.length || this.state.initialState == null) this.state.initialState = this.rtr.snapshot();
1381
1650
  this.lastTimestamp = performance.now();
1382
1651
  this.state.set("currentFrame", (v = 0) => clamp(0, v, this.state.history.length), { signal: this.signal, immediate: true });
1383
- this.config.on("paths", this.handlePathsState, { signal: this.signal, immediate: true });
1652
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
1384
1653
  !this.state.paused && this.play();
1385
1654
  }
1386
- handlePathsState({ value: paths = ["*"], oldValue: prevs = ["*"] }) {
1387
- for (const p of prevs) this.rtr.off(p, this.record);
1388
- for (const p of paths) this.rtr.off(p, this.record), this.rtr.on(p, this.record, { signal: this.signal });
1655
+ onAttach(rtr, rid) {
1656
+ rtr.config.referenceTracking = rtr.config.smartCloning = rtr.config.eventTimeStamps = true;
1657
+ if (!this.state.history.length || !this.state.initialState[rid]) this.state.initialState[rid] = rtr.snapshot();
1658
+ for (const p of this.config.paths ?? wpArr) rtr.on(p, this.record, { signal: this.signal });
1659
+ }
1660
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
1661
+ for (const rtr of this.rtrs.values()) {
1662
+ for (const p of prevs) rtr.off(p, this.record);
1663
+ for (const p of paths) rtr.off(p, this.record), rtr.on(p, this.record, { signal: this.signal });
1664
+ }
1389
1665
  }
1390
1666
  /** Chronicling the lifecycle of the system, Captures the essence of every mutation wave that bubbles up. */
1391
- record(e) {
1667
+ record(e, rid = this.rids.get(e.reactor)) {
1392
1668
  if (!this.state.paused) return;
1393
- if (this.state.currentFrame < this.state.history.length) this.state.history = this.state.history.slice(0, this.state.currentFrame);
1394
- if (this.state.history.length >= this.config.maxHistoryLength) this.state.history = this.state.history.slice(1);
1395
- 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 });
1669
+ if (this.state.currentFrame < this.state.history.length) fanout(this.state, "history", this.state.history.slice(0, this.state.currentFrame), { atomic: true });
1670
+ if (this.state.history.length >= this.config.maxHistoryLength) fanout(this.state, "history", this.state.history.slice(1), { atomic: true });
1671
+ 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 };
1672
+ e.rejected && (en.rejected = e.rejected), !e.target.hadKey && (en.hadKey = false), this.state.history.push(en);
1396
1673
  this.state.currentFrame = this.state.history.length;
1397
1674
  this.lastTimestamp = e.timestamp;
1398
1675
  }
@@ -1401,7 +1678,7 @@ var sia = (() => {
1401
1678
  this.pause();
1402
1679
  this.playbackTimeoutId = -1;
1403
1680
  this.state.history.length = this.state.currentFrame = 0;
1404
- this.state.initialState = this.rtr.snapshot();
1681
+ this.state.initialState = Object.fromEntries(this.rtrs.entries().map(([rid, rtr]) => [rid, rtr.snapshot()]));
1405
1682
  this.lastTimestamp = performance.now();
1406
1683
  }
1407
1684
  // ===========================================================================
@@ -1414,12 +1691,13 @@ var sia = (() => {
1414
1691
  while (this.state.currentFrame !== target) {
1415
1692
  const e = this.state.history[forward ? this.state.currentFrame : this.state.currentFrame - 1];
1416
1693
  if (!e) break;
1417
- if (forward) e.type === "delete" ? deleteAny(this.rtr.core, e.path) : setAny(this.rtr.core, e.path, deepClone(e.value, this.rtr.config));
1418
- else !e.hadKey ? deleteAny(this.rtr.core, e.path) : setAny(this.rtr.core, e.path, deepClone(e.oldValue, this.rtr.config));
1694
+ const rtr = this.rtrs.get(e.rid) || this.rtrs.values().next().value;
1695
+ if (forward) e.type === "delete" ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.value, rtr.config));
1696
+ else e.hadKey === false ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.oldValue, rtr.config));
1419
1697
  forward ? this.state.currentFrame++ : this.state.currentFrame--;
1420
- if (e.rejected) this.rtr.log(`[Reactor ${this.name} Plug] ${forward ? "Replaying" : "Reversing"} REJECTED intent at "${e.path}"`);
1698
+ if (e.rejected) rtr.log(`[Reactor ${this.name} Module] ${forward ? "Replaying" : "Reversing"} REJECTED intent at "${e.path}"`);
1421
1699
  }
1422
- this.rtr.tick();
1700
+ for (const rtr of this.rtrs.values()) rtr.tick();
1423
1701
  if (!keepShield) this.state.paused = true;
1424
1702
  }
1425
1703
  /** Step through time, Moves the playhead and teleports the state. */
@@ -1438,9 +1716,9 @@ var sia = (() => {
1438
1716
  async automove(forward = true) {
1439
1717
  this.state.paused = false;
1440
1718
  while ((forward ? this.state.currentFrame < this.state.history.length : this.state.currentFrame > 0) && !this.state.paused) {
1441
- const currIndex = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? currIndex + 1 : currIndex - 1];
1719
+ const idx = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? idx + 1 : idx - 1];
1442
1720
  this.jumpTo(this.state.currentFrame + (forward ? 1 : -1), true);
1443
- if (e?.timedelta > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), Math.min(e.timedelta, this.config.maxPlaybackDelay), this.signal));
1721
+ if (e?.deltat > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), Math.min(e.deltat, this.config.maxPlaybackDelay), this.signal));
1444
1722
  }
1445
1723
  this.state.paused = true;
1446
1724
  }
@@ -1455,27 +1733,19 @@ var sia = (() => {
1455
1733
  // ===========================================================================
1456
1734
  /** Exports the current session as a JSON string. */
1457
1735
  export(replacer, space) {
1458
- try {
1459
- return JSON.stringify(this.state, replacer, space);
1460
- } catch (e) {
1461
- return this.rtr.log(`[Reactor ${this.name} Plug] Failed to export session`), "";
1462
- }
1736
+ return JSON.stringify(this.state, replacer, space);
1463
1737
  }
1464
1738
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
1465
- import(json) {
1466
- try {
1467
- setAny(this.state, "*", JSON.parse(json));
1468
- this.lastTimestamp = performance.now();
1469
- const resume = !this.state.paused, target = this.state.currentFrame;
1470
- this.state.paused = false;
1471
- setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config)), this.rtr.tick();
1472
- this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
1473
- } catch (e) {
1474
- this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`);
1475
- }
1739
+ import(json, reviver) {
1740
+ setAny(this.state, "*", JSON.parse(json, reviver));
1741
+ this.lastTimestamp = performance.now();
1742
+ const resume = !this.state.paused, target = this.state.currentFrame;
1743
+ this.state.paused = false;
1744
+ for (const [rid, rtr] of this.rtrs) setAny(rtr.core, "*", deepClone(this.state.initialState[rid], rtr.config)), rtr.tick();
1745
+ this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
1476
1746
  }
1477
1747
  };
1478
- var TIME_TRAVEL_PLUGIN_BUILD = { maxPlaybackDelay: 2e3 };
1748
+ var TIME_TRAVEL_MODULE_BUILD = { maxPlaybackDelay: 2e3 };
1479
1749
 
1480
1750
  // src/ts/adapters/vanilla.ts
1481
1751
  var vanilla_exports = {};
@@ -1590,9 +1860,9 @@ var sia = (() => {
1590
1860
  * const stop = atrkr.callback(() => console.log("changed")); // re-run after when ".user.name" changes
1591
1861
  * @example Packaged Customization
1592
1862
  * const atrkr = new Autotracker(); // no reactor passed
1593
- * withTracker(atrkr, () => state.user.name); // import `withTracker` first
1863
+ * withTracker(atrkr, () => state.user.name); // import `withTracker` too
1594
1864
  * const stop = atrkr.callback(() => console.log("sync"), { sync: true }); // re-run immediately when ".user.name" changes, works on any path used from any reactor state
1595
- * @example Extensive customization
1865
+ * @example Extensive Customization
1596
1866
  * atrkr.unblock();
1597
1867
  * const prev = CTX.autotracker;
1598
1868
  * CTX.autotracker = atrkr; // import CTX first
@@ -1640,8 +1910,8 @@ var sia = (() => {
1640
1910
 
1641
1911
  // src/ts/adapters/vanilla/TimeTravelOverlay.ts
1642
1912
  var keys = {
1643
- overrides: ["Ctrl+z", "Cmd+z", "Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z", "Ctrl+g", "Cmd+g", ",", ".", "ArrowLeft", "ArrowRight", "Space", "Alt+Space", "Escape", "Delete", "e", "i", "c"],
1644
- shortcuts: { undo: ["Ctrl+z", "Cmd+z"], redo: ["Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z"], genesis: ["Ctrl+g", "Cmd+g"], prevFrame: ",", nextFrame: ".", skipBwd: "ArrowLeft", skipFwd: "ArrowRight", playPause: "Space", rewind: "Alt+Space", closeOverlay: "Escape", clrHistory: "Delete", export: "e", import: "i", clear: "c" }
1913
+ overrides: ["Ctrl+z", "Cmd+z", "Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z", "Home", "End", ",", ".", "ArrowLeft", "ArrowRight", "Space", "Alt+Space", "Escape", "Delete", "e", "i", "c"],
1914
+ shortcuts: { undo: ["Ctrl+z", "Cmd+z"], redo: ["Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z"], genesis: "Home", ending: "End", prevFrame: ",", nextFrame: ".", skipBwd: "ArrowLeft", skipFwd: "ArrowRight", playPause: "Space", rewind: "Alt+Space", closeOverlay: "Escape", clrHistory: "Delete", export: "e", import: "i", clear: "c" }
1645
1915
  };
1646
1916
  var TimeTravelOverlay = class _TimeTravelOverlay {
1647
1917
  static count = 0;
@@ -1652,22 +1922,22 @@ var sia = (() => {
1652
1922
  els;
1653
1923
  clups = [];
1654
1924
  keyup;
1655
- /** Creates a docked TimeTravel overlay bound to a plugin instance.
1656
- * @param time TimeTravel plugin instance that owns timeline operations.
1925
+ /** Creates a docked TimeTravel overlay bound to a module instance.
1926
+ * @param time TimeTravel module instance that owns timeline operations.
1657
1927
  * @param build Optional initial overlay config overrides.
1658
1928
  */
1659
1929
  constructor(time, build = {}) {
1660
1930
  this.time = time;
1661
1931
  this.config = reactive({ title: `Time Travel Overlay ${this.index = ++_TimeTravelOverlay.count}`, ...build });
1662
1932
  this.state.open = !!this.config.startOpen;
1663
- const s = this.time.state, host = createEl("div", { className: "tt-overlay-host" }), toggle = createEl("button", { className: "tt-overlay-toggle", type: "button", onclick: () => this.state.open = !this.state.open }), panel = createEl("aside", { className: "tt-overlay", ariaLabel: "time travel overlay" }), title = createEl("div", { className: "title" }), frame = createEl("span", { className: "muted" }), clrHistory = createEl("button", { textContent: `Clear History${formatKeyForDisplay(keys.shortcuts.clrHistory)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clrHistory, false), onclick: () => (this.time.clear(), this.state.import = "") }), undo = createEl("button", { textContent: `Undo${formatKeyForDisplay(keys.shortcuts.undo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.undo, false), onclick: this.time.undo }), redo = createEl("button", { textContent: `Redo${formatKeyForDisplay(keys.shortcuts.redo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.redo, false), onclick: this.time.redo }), genesis = createEl("button", { textContent: `Genesis${formatKeyForDisplay(keys.shortcuts.genesis[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.genesis, false), onclick: () => this.time.jumpTo(0) }), playPause = createEl("button", { onclick: () => this.time[s.paused ? "play" : "pause"](), ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.playPause, false) }), rewind = createEl("button", { textContent: `Rewind${formatKeyForDisplay(keys.shortcuts.rewind)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.rewind, false), onclick: this.time.rewind }), range = createEl("input", { type: "range", min: "0", max: "0", value: "0", title: "time travel frame", ariaLabel: "time travel frame", oninput: () => this.time.jumpTo(Number(range.value)) }), exp = createEl("button", { textContent: `Export${formatKeyForDisplay(keys.shortcuts.export)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.export, false), onclick: () => this.state.import = this.time.export(null, 2) }), imp = createEl("button", { textContent: `Import${formatKeyForDisplay(keys.shortcuts.import)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.import, false), onclick: () => this.state.import.trim().length && this.time.import(this.state.import) }), clr = createEl("button", { textContent: `Clear${formatKeyForDisplay(keys.shortcuts.clear)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clear, false), onclick: () => this.state.import = "" }), payload = createEl("textarea", { className: "tt-io", readOnly: true, placeholder: "current payload json", title: "current payload" }), io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", oninput: () => this.state.import = io.value }), foot = createEl("p", { className: "tt-footnote", textContent: "Want this in your app? " }), link = createEl("a", { target: "_blank", rel: "noreferrer noopener", textContent: "sia-reactor", href: "https://www.npmjs.com/package/sia-reactor" }), box = createEl("div", { className: "tt-status-box" }), status = createEl("div", { className: "tt-status-row" }), row1 = createEl("div", { className: "tt-row" }), row2 = createEl("div", { className: "tt-row" }), row3 = createEl("div", { className: "tt-row" });
1933
+ const s = this.time.state, host = createEl("div", { className: "tt-overlay-host" }), toggle = createEl("button", { className: "tt-overlay-toggle", type: "button", onclick: () => this.state.open = !this.state.open }), panel = createEl("aside", { className: "tt-overlay", ariaLabel: "time travel overlay" }), title = createEl("div", { className: "title" }), frame = createEl("span", { className: "muted" }), clrHistory = createEl("button", { textContent: `Clear History${formatKeyForDisplay(keys.shortcuts.clrHistory)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clrHistory, false), onclick: () => (this.time.clear(), this.state.import = "") }), undo = createEl("button", { textContent: `Undo${formatKeyForDisplay(keys.shortcuts.undo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.undo, false), onclick: this.time.undo }), redo = createEl("button", { textContent: `Redo${formatKeyForDisplay(keys.shortcuts.redo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.redo, false), onclick: this.time.redo }), genesis = createEl("button", { textContent: `Genesis${formatKeyForDisplay(keys.shortcuts.genesis)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.genesis, false), onclick: () => this.time.jumpTo(0) }), playPause = createEl("button", { onclick: () => this.time[s.paused ? "play" : "pause"](), ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.playPause, false) }), rewind = createEl("button", { textContent: `Rewind${formatKeyForDisplay(keys.shortcuts.rewind)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.rewind, false), onclick: this.time.rewind }), range = createEl("input", { type: "range", min: "0", max: "0", value: "0", title: "time travel frame", ariaLabel: "time travel frame", oninput: () => this.time.jumpTo(Number(range.value)) }), exp = createEl("button", { textContent: `Export${formatKeyForDisplay(keys.shortcuts.export)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.export, false), onclick: () => this.state.import = this.time.export(null, 2) }), imp = createEl("button", { textContent: `Import${formatKeyForDisplay(keys.shortcuts.import)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.import, false), onclick: () => this.state.import.trim().length && this.time.import(this.state.import) }), clr = createEl("button", { textContent: `Clear${formatKeyForDisplay(keys.shortcuts.clear)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clear, false), onclick: () => this.state.import = "" }), payload = createEl("textarea", { className: "tt-io", readOnly: true, placeholder: "current payload json", title: "current payload" }), io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", oninput: () => this.state.import = io.value }), foot = createEl("p", { className: "tt-footnote", textContent: "Want this in your app? " }), link = createEl("a", { target: "_blank", rel: "noreferrer noopener", textContent: "sia-reactor", href: "https://www.npmjs.com/package/sia-reactor" }), box = createEl("div", { className: "tt-status-box" }), status = createEl("div", { className: "tt-status-row" }), row1 = createEl("div", { className: "tt-row" }), row2 = createEl("div", { className: "tt-row" }), row3 = createEl("div", { className: "tt-row" });
1664
1934
  status.append((box.append(frame), box), clrHistory);
1665
1935
  panel.append(title, status, (row1.append(undo, redo, genesis), row1), (row2.append(playPause, rewind), row2), payload, range, (row3.append(exp, imp, clr), row3), io, (foot.appendChild(link), foot));
1666
1936
  host.append(toggle, panel);
1667
1937
  this.els = { host, toggle, panel, title, frame, clrHistory, undo, redo, genesis, playPause, rewind, range, exp, imp, clr, payload, io };
1668
1938
  this.keyup = (e) => {
1669
- const a = this.state.open && keyEventAllowed(e, keys);
1670
- a === "undo" ? this.time.undo() : a === "redo" ? this.time.redo() : a === "genesis" ? this.time.jumpTo(0) : a === "prevFrame" ? this.time.step(1, false) : a === "nextFrame" ? this.time.step(1, true) : a === "skipBwd" ? this.time.step(5, false) : a === "skipFwd" ? this.time.step(5, true) : a === "rewind" ? this.time.rewind() : a === "playPause" ? this.time[s.paused ? "play" : "pause"]() : a === "clrHistory" ? this.time.clear() : a === "closeOverlay" ? this.state.open = false : a === "export" ? this.state.import = this.time.export() : a === "import" ? this.state.import.trim().length && this.time.import(this.state.import) : a === "clear" && (this.state.import = "");
1939
+ const a = this.state.open && (this.config.devOnly ? CTX.isDevEnv : true) && keyEventAllowed(e, keys);
1940
+ a === "undo" ? this.time.undo() : a === "redo" ? this.time.redo() : a === "genesis" ? this.time.jumpTo(0) : a === "ending" ? this.time.jumpTo(s.history.length) : a === "prevFrame" ? this.time.step(1, false) : a === "nextFrame" ? this.time.step(1, true) : a === "skipBwd" ? this.time.step(5, false) : a === "skipFwd" ? this.time.step(5, true) : a === "rewind" ? this.time.rewind() : a === "playPause" ? this.time[s.paused ? "play" : "pause"]() : a === "clrHistory" ? this.time.clear() : a === "closeOverlay" ? this.state.open = false : a === "export" ? this.state.import = this.time.export() : a === "import" ? this.state.import.trim().length && this.time.import(this.state.import) : a === "clear" && (this.state.import = "");
1671
1941
  };
1672
1942
  window.addEventListener("keydown", this.keyup);
1673
1943
  const sync = [
@@ -1682,7 +1952,8 @@ var sia = (() => {
1682
1952
  effect(() => {
1683
1953
  frame.textContent = `Frame: ${s.currentFrame} / ${s.history.length}`;
1684
1954
  range.disabled = clrHistory.disabled = !s.history.length;
1685
- genesis.disabled = rewind.disabled = undo.disabled = !s.currentFrame;
1955
+ genesis.disabled = undo.disabled = !s.currentFrame;
1956
+ rewind.disabled = !s.paused || !s.currentFrame;
1686
1957
  playPause.disabled = redo.disabled = s.currentFrame >= s.history.length;
1687
1958
  range.max = String(s.history.length);
1688
1959
  range.value = String(Math.min(s.currentFrame, s.history.length));
@@ -1696,7 +1967,7 @@ var sia = (() => {
1696
1967
  this.clups.push(...sync);
1697
1968
  }
1698
1969
  destroy() {
1699
- this.clups.forEach((fn) => fn());
1970
+ for (const clup of this.clups) clup();
1700
1971
  this.keyup && window.removeEventListener("keydown", this.keyup);
1701
1972
  this.els.host.remove();
1702
1973
  nuke(this), --_TimeTravelOverlay.count;