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
@@ -0,0 +1,619 @@
1
+ import {
2
+ getReactor,
3
+ reactive
4
+ } from "./chunk-MSTHQVNK.js";
5
+ import {
6
+ clamp,
7
+ guardAllMethods,
8
+ guardMethod,
9
+ setTimeout
10
+ } from "./chunk-P37ADJMM.js";
11
+ import {
12
+ NIL,
13
+ NOOP,
14
+ deepClone,
15
+ deleteAny,
16
+ fanout,
17
+ fanoutOptsArr,
18
+ getAny,
19
+ isObj,
20
+ mergeObjs,
21
+ parseEvtOpts,
22
+ setAny
23
+ } from "./chunk-MKL3JUPO.js";
24
+
25
+ // src/ts/modules/base.ts
26
+ var wpArr = ["*"];
27
+ var BaseReactorModule = class {
28
+ static moduleName;
29
+ get name() {
30
+ return this.constructor.moduleName;
31
+ }
32
+ ac = new AbortController();
33
+ signal = this.ac.signal;
34
+ rtrs = /* @__PURE__ */ new Map();
35
+ rids = /* @__PURE__ */ new WeakMap();
36
+ // for quick 0(1) lookups over iteration
37
+ wired = false;
38
+ config;
39
+ state;
40
+ constructor(config, rtr, state) {
41
+ guardAllMethods(this, this.guard);
42
+ this.config = isObj(config) ? reactive(config) : config;
43
+ this.state = isObj(state) ? reactive(state) : state;
44
+ rtr && this.attach(rtr);
45
+ }
46
+ /**
47
+ * Connect to a `Reactor` instance, allows managing multiple reactors if needed.
48
+ * @param target `Reactor` instance or `reactive()` object to connect to.
49
+ * @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.
50
+ * @returns Current `ReactorModule` instance for fluent chaining.
51
+ * @example
52
+ * const mod = new MyModule().attach(state1).attach(state2); // implicit index-based ids by default, add a .setup() or `Reactor.use()` when ready for init.
53
+ * @example
54
+ * const persist = new PersistModule(config).attach(sessState, "session").attach(adminState, "session.admin"); // don't use "*", causes de-serialization issues.
55
+ */
56
+ attach(target, id = this.rtrs.size) {
57
+ const rtr = getReactor(target);
58
+ if (!rtr || this.rtrs.has(id)) return this;
59
+ return this.rids.set((this.rtrs.set(id, rtr), rtr), id), this.onAttach(rtr, id), this;
60
+ }
61
+ onAttach(_rtr, _rid) {
62
+ }
63
+ /**
64
+ * Entry point called to initialize module wiring, calls `.attach(target, id)` first, `Reactor.use()` calls this internally.
65
+ * Should run as last in `.attach()` chain or after all desired reactors if using multiple; so wiring is done safely after.
66
+ * @param target `Reactor` instance or `reactive()` object to connect to.
67
+ * @param id Optional id for the reactor, prefer over default implicit index id when managing multiple reactors.
68
+ * @returns Current `ReactorModule` instance for fluent chaining.
69
+ * @example
70
+ * const mod = new MyModule().attach(state1).setup(state2); // if using multiple, this should run last; with same params as `.attach()` for a shorter chain
71
+ */
72
+ setup(target, id) {
73
+ return this.attach(target, id), !this.wired && (this.wire(), this.wired = true), this;
74
+ }
75
+ destroy() {
76
+ this.ac.abort();
77
+ this.onDestroy?.();
78
+ }
79
+ /**
80
+ * Wraps a function with module-scoped error logging.
81
+ * Use this when creating functions dynamically (for example, before attaching an anonymous listener on the fly).
82
+ * @example
83
+ * window.addEventListener("resize", this.guard(() => this.syncLayout(true)), { signal: this.signal });
84
+ */
85
+ guard = (fn) => {
86
+ return guardMethod(fn, (e) => this.rtrs.values().next().value?.log(`[Reactor "${this.name}" Module] Error: ${e}`));
87
+ };
88
+ // `()=>{}`: needs to be bounded even before initialization
89
+ };
90
+
91
+ // src/ts/utils/store.ts
92
+ var BaseStorageAdapter = class {
93
+ name = "StorageAdapter";
94
+ config;
95
+ 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})`);
96
+ constructor(config) {
97
+ this.config = { debug: false, ...config };
98
+ }
99
+ };
100
+ var StorageAdapter = class extends BaseStorageAdapter {
101
+ name = "SyncStorageAdapter";
102
+ };
103
+ var AsyncStorageAdapter = class extends BaseStorageAdapter {
104
+ name = "AsyncStorageAdapter";
105
+ };
106
+ var LocalStorageAdapter = class extends StorageAdapter {
107
+ name = "LocalStorage";
108
+ /**
109
+ * Reads and parses a value from localStorage.
110
+ * @param key Storage key.
111
+ * @returns Parsed value, or `undefined` when missing/unreadable.
112
+ */
113
+ get(key, reviver = this.config.reviver) {
114
+ try {
115
+ const v = localStorage.getItem(key);
116
+ return v ? JSON.parse(v, reviver) : void 0;
117
+ } catch {
118
+ return void 0;
119
+ }
120
+ }
121
+ /**
122
+ * Serializes and writes a value to localStorage.
123
+ * @param key Storage key.
124
+ * @param value Value to serialize.
125
+ * @returns `true` when write succeeds, else `false`.
126
+ */
127
+ set(key, value, replacer = this.config.replacer) {
128
+ try {
129
+ return localStorage.setItem(key, JSON.stringify(value, replacer)), true;
130
+ } catch (e) {
131
+ return this.warn("setItem", void 0, key), false;
132
+ }
133
+ }
134
+ /**
135
+ * Removes a single key from localStorage.
136
+ * @param key Storage key.
137
+ * @returns `true` when removal succeeds, else `false`.
138
+ */
139
+ remove(key) {
140
+ try {
141
+ return localStorage.removeItem(key), true;
142
+ } catch (e) {
143
+ return this.warn("removeItem", void 0, key), false;
144
+ }
145
+ }
146
+ /**
147
+ * Clears all localStorage entries for the current origin.
148
+ * @returns `true` when clear succeeds, else `false`.
149
+ */
150
+ clear() {
151
+ try {
152
+ return localStorage.clear(), true;
153
+ } catch (e) {
154
+ return this.warn("clear", void 0), false;
155
+ }
156
+ }
157
+ };
158
+ var SessionStorageAdapter = class extends StorageAdapter {
159
+ name = "SessionStorage";
160
+ /**
161
+ * Reads and parses a value from sessionStorage.
162
+ * @param key Storage key.
163
+ * @returns Parsed value, or `undefined` when missing/unreadable.
164
+ */
165
+ get(key, reviver = this.config.reviver) {
166
+ try {
167
+ const v = sessionStorage.getItem(key);
168
+ return v ? JSON.parse(v, reviver) : void 0;
169
+ } catch {
170
+ return void 0;
171
+ }
172
+ }
173
+ /**
174
+ * Serializes and writes a value to sessionStorage.
175
+ * @param key Storage key.
176
+ * @param value Value to serialize.
177
+ * @returns `true` when write succeeds, else `false`.
178
+ */
179
+ set(key, value, replacer = this.config.replacer) {
180
+ try {
181
+ return sessionStorage.setItem(key, JSON.stringify(value, replacer)), true;
182
+ } catch (e) {
183
+ return this.warn("setItem", void 0, key), false;
184
+ }
185
+ }
186
+ /**
187
+ * Removes a single key from sessionStorage.
188
+ * @param key Storage key.
189
+ * @returns `true` when removal succeeds, else `false`.
190
+ */
191
+ remove(key) {
192
+ try {
193
+ return sessionStorage.removeItem(key), true;
194
+ } catch (e) {
195
+ return this.warn("removeItem", void 0, key), false;
196
+ }
197
+ }
198
+ /**
199
+ * Clears all sessionStorage entries for the current tab session.
200
+ * @returns `true` when clear succeeds, else `false`.
201
+ */
202
+ clear() {
203
+ try {
204
+ return sessionStorage.clear(), true;
205
+ } catch (e) {
206
+ return this.warn("clear", void 0), false;
207
+ }
208
+ }
209
+ };
210
+ var MemoryAdapter = class extends StorageAdapter {
211
+ name = "Memory";
212
+ constructor(build) {
213
+ super({ store: /* @__PURE__ */ new Map(), ...build });
214
+ }
215
+ /**
216
+ * Reads and parses a value from memory storage.
217
+ * @param key Storage key.
218
+ * @returns Parsed value, or `undefined` when missing/unreadable.
219
+ */
220
+ get(key, reviver = this.config.reviver) {
221
+ try {
222
+ const v = this.config.store.get(key);
223
+ return v ? JSON.parse(v, reviver) : void 0;
224
+ } catch {
225
+ return void 0;
226
+ }
227
+ }
228
+ /**
229
+ * Serializes and writes a value to memory storage.
230
+ * @param key Storage key.
231
+ * @param value Value to serialize.
232
+ * @returns `true` when write succeeds, else `false`.
233
+ */
234
+ set(key, value, replacer = this.config.replacer) {
235
+ try {
236
+ return this.config.store.set(key, JSON.stringify(value, replacer)), true;
237
+ } catch (e) {
238
+ return this.warn("set", void 0, key), false;
239
+ }
240
+ }
241
+ /**
242
+ * Removes a single key from memory storage.
243
+ * @param key Storage key.
244
+ * @returns `true` when removal succeeds, else `false`.
245
+ */
246
+ remove(key) {
247
+ try {
248
+ return this.config.store.delete(key), true;
249
+ } catch (e) {
250
+ return this.warn("remove", void 0, key), false;
251
+ }
252
+ }
253
+ /**
254
+ * Clears all entries from memory storage.
255
+ * @returns `true` when clear succeeds, else `false`.
256
+ */
257
+ clear() {
258
+ try {
259
+ return this.config.store.clear(), true;
260
+ } catch (e) {
261
+ return this.warn("clear", void 0), false;
262
+ }
263
+ }
264
+ };
265
+ var CookieAdapter = class extends StorageAdapter {
266
+ name = "Cookie";
267
+ 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}` : ""}`;
268
+ constructor(build) {
269
+ super({ secure: "undefined" !== typeof window && location.protocol === "https:", ...COOKIE_ADAPTER_BUILD, ...build });
270
+ }
271
+ /**
272
+ * Reads and parses a cookie visible to the current page scope.
273
+ * @param key Cookie key.
274
+ * @returns Parsed value, or `undefined` when missing/unreadable.
275
+ */
276
+ get(key, reviver = this.config.reviver) {
277
+ try {
278
+ const k = encodeURIComponent(key) + "=";
279
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
280
+ if (!pair.startsWith(k)) continue;
281
+ return JSON.parse(decodeURIComponent(pair.slice(k.length)), reviver);
282
+ }
283
+ return void 0;
284
+ } catch {
285
+ return void 0;
286
+ }
287
+ }
288
+ /**
289
+ * Writes a cookie with optional per-call scope/lifetime overrides.
290
+ * @param key Cookie key.
291
+ * @param value Value to serialize.
292
+ * @param opts Optional per-call cookie options.
293
+ * @returns `true` when write succeeds, else `false`.
294
+ */
295
+ set(key, value, opts, replacer = this.config.replacer) {
296
+ try {
297
+ return document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(value, replacer))}; ${this.deets(opts)}`, true;
298
+ } catch {
299
+ return this.warn("set", void 0, key), false;
300
+ }
301
+ }
302
+ /**
303
+ * Removes a cookie key using matching scope attributes.
304
+ * @param key Cookie key.
305
+ * @param opts Optional per-call scope overrides.
306
+ * @returns `true` when removal succeeds, else `false`.
307
+ */
308
+ remove(key, opts) {
309
+ try {
310
+ return document.cookie = `${encodeURIComponent(key)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`, true;
311
+ } catch {
312
+ return this.warn("remove", void 0, key), false;
313
+ }
314
+ }
315
+ /**
316
+ * Attempts to remove all visible cookie keys for the given scope.
317
+ * @param opts Optional per-call scope overrides.
318
+ * @returns `true` when clear succeeds, else `false`.
319
+ */
320
+ clear(opts) {
321
+ try {
322
+ for (const pair of document.cookie ? document.cookie.split("; ") : []) {
323
+ const idx = pair.indexOf("=");
324
+ document.cookie = `${idx === -1 ? pair : pair.slice(0, idx)}=; ${this.deets({ ...opts, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })}`;
325
+ }
326
+ return true;
327
+ } catch {
328
+ return this.warn("clear"), false;
329
+ }
330
+ }
331
+ };
332
+ var IndexedDBAdapter = class extends AsyncStorageAdapter {
333
+ name = "IndexedDB";
334
+ db;
335
+ constructor(build) {
336
+ super({ ...INDEXED_DB_ADAPTER_BUILD, ...build });
337
+ }
338
+ /**
339
+ * Returns a connected IndexedDB instance, opening it when needed.
340
+ * @returns Connected database handle.
341
+ */
342
+ async idb() {
343
+ const idb = this.config.onidb();
344
+ if (idb || this.db) return Promise.resolve(idb || this.db);
345
+ return new Promise((res, rej) => {
346
+ const req = indexedDB.open(this.config.dbName, this.config.version);
347
+ req.onupgradeneeded = (e) => (this.config.onupgradeneeded(req.result, e), this.config.stores.forEach((s) => !req.result.objectStoreNames.contains(s) && req.result.createObjectStore(s)));
348
+ 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));
349
+ req.onerror = (e) => (this.config.onerror(req.error, e), this.warn("open", "Something went wrong"), rej(req.error));
350
+ req.onblocked = (e) => (this.config.onblocked(e), this.warn("open", "Close other tabs for updates"));
351
+ });
352
+ }
353
+ /**
354
+ * Reads a value by key from an object store.
355
+ * @param key Record key.
356
+ * @param store Optional object-store override.
357
+ * @returns Stored value, or `undefined` when missing/unreadable.
358
+ */
359
+ async get(key, store = this.config.stores[0], options = this.config) {
360
+ try {
361
+ const req = (await this.idb()).transaction(store, "readonly", options).objectStore(store).get(key);
362
+ return new Promise((res) => req.onsuccess = () => res(req.result));
363
+ } catch {
364
+ return this.warn("get", void 0, store), void 0;
365
+ }
366
+ }
367
+ /**
368
+ * Writes a value by key into an object store.
369
+ * @param key Record key.
370
+ * @param value Value to store.
371
+ * @param store Optional object-store override.
372
+ * @returns `true` when write succeeds, else `false`.
373
+ */
374
+ async set(key, value, store = this.config.stores[0], options = this.config) {
375
+ try {
376
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).put(value, key);
377
+ return new Promise((res) => req.onsuccess = () => res(true));
378
+ } catch (e) {
379
+ return this.warn("put", void 0, store), false;
380
+ }
381
+ }
382
+ /**
383
+ * Deletes a value by key from an object store.
384
+ * @param key Record key.
385
+ * @param store Optional object-store override.
386
+ * @returns `true` when delete succeeds, else `false`.
387
+ */
388
+ async remove(key, store = this.config.stores[0], options = this.config) {
389
+ try {
390
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).delete(key);
391
+ return new Promise((res) => req.onsuccess = () => res(true));
392
+ } catch (e) {
393
+ return this.warn("delete", void 0, store), false;
394
+ }
395
+ }
396
+ /**
397
+ * Clears one or more object stores.
398
+ * @param stores Store name or list of store names to clear.
399
+ * @returns `true` when all clears succeed, else `false`.
400
+ */
401
+ async clear(stores = this.config.stores, options = this.config) {
402
+ let success = true;
403
+ for (const store of Array.isArray(stores) ? stores : [stores])
404
+ try {
405
+ const req = (await this.idb()).transaction(store, "readwrite", options).objectStore(store).clear();
406
+ await new Promise((res) => req.onsuccess = () => res(true));
407
+ } catch (e) {
408
+ this.warn("clear", void 0, store), success = false;
409
+ }
410
+ return success;
411
+ }
412
+ };
413
+ var COOKIE_ADAPTER_BUILD = { path: "/", sameSite: "Lax", domain: void 0, debug: false };
414
+ var INDEXED_DB_ADAPTER_BUILD = { dbName: "REACTOR_IDB", stores: ["VAULT"], version: 1, onidb: NOOP, onupgradeneeded: NOOP, onversionchange: NOOP, onsuccess: NOOP, onerror: NOOP, onblocked: NOOP };
415
+
416
+ // src/ts/modules/persist.ts
417
+ var PersistModule = class extends BaseReactorModule {
418
+ static moduleName = "persist";
419
+ adapter;
420
+ hydrateSeq = 0;
421
+ saveTimeoutId = 0;
422
+ get payload() {
423
+ let res = this.rtrs.size > 1 ? {} : void 0;
424
+ for (const [rid, rtr] of this.rtrs) {
425
+ 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;
426
+ this.rtrs.size > 1 ? setAny(res, rid, val) : res = val;
427
+ }
428
+ return res;
429
+ }
430
+ constructor(config, rtr) {
431
+ super(mergeObjs(PERSIST_MODULE_BUILD, config), rtr, { hydrated: false });
432
+ }
433
+ wire() {
434
+ "undefined" !== typeof window && window.addEventListener("pagehide", this.onDestroy, { signal: this.signal });
435
+ "undefined" !== typeof document && document.addEventListener("visibilitychange", () => document.visibilityState === "hidden" && this.onDestroy(), { signal: this.signal });
436
+ this.config.on("adapter", this.handleAdapter, { signal: this.signal, immediate: true });
437
+ this.config.on("disabled", this.handleDisabled, { signal: this.signal, immediate: true });
438
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
439
+ }
440
+ onAttach(rtr) {
441
+ 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);
442
+ }
443
+ async handleAdapter({ value = LocalStorageAdapter }) {
444
+ const seq = ++this.hydrateSeq;
445
+ if (this.adapter && value === this.adapter.constructor) return;
446
+ this.state.hydrated = false;
447
+ this.adapter?.remove(this.config.key);
448
+ this.adapter = "function" === typeof value ? new value({ debug: !!this.rtrs.values().next().value?.canLog }) : value;
449
+ try {
450
+ let saved = this.adapter.get(this.config.key);
451
+ const isAsync = saved instanceof Promise, { depth, merge = true } = parseEvtOpts(this.config.fanout ?? isAsync, fanoutOptsArr, "depth");
452
+ saved = !isAsync ? saved : await saved;
453
+ if (seq !== this.hydrateSeq || !saved) return;
454
+ for (const [rid, rtr] of this.rtrs) {
455
+ const entry = this.rtrs.size > 1 ? getAny(saved, rid) : saved;
456
+ if (!entry) continue;
457
+ const set = (p, news, olds) => (depth ? fanout : setAny)(rtr.core, p, merge ? mergeObjs(news, olds) : olds, depth ? { depth, crossRealms: rtr.config.crossRealms } : void 0);
458
+ for (const p of this.config.paths ?? wpArr) set(p, getAny(rtr.core, p), getAny(entry, p));
459
+ }
460
+ for (const rtr of this.rtrs.values()) rtr.tick(depth ? "*" : this.config.paths ?? "*");
461
+ } finally {
462
+ if (seq === this.hydrateSeq) this.state.hydrated = true;
463
+ }
464
+ }
465
+ handleDisabled({ value }) {
466
+ for (const rtr of this.rtrs.values()) this.onAttach(rtr);
467
+ value && this.adapter?.remove(this.config.key);
468
+ }
469
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
470
+ for (const rtr of this.rtrs.values()) {
471
+ for (const p of prevs) rtr.off(p, this.handleSave);
472
+ for (const p of paths) rtr.off(p, this.handleSave), !this.config.disabled && rtr.on(p, this.handleSave, { signal: this.signal, immediate: true });
473
+ }
474
+ }
475
+ handleSave(e) {
476
+ if (!this.state.hydrated) return e.stopImmediatePropagation();
477
+ if (!this.saveTimeoutId) this.saveTimeoutId = setTimeout(() => (this.adapter.set(this.config.key, this.payload), this.saveTimeoutId = 0), this.config.throttle, this.signal);
478
+ }
479
+ /** Clears persisted payload for this module instance and drops any pending save. */
480
+ clear() {
481
+ clearTimeout(this.saveTimeoutId);
482
+ this.saveTimeoutId = -1;
483
+ for (const rtr of this.rtrs.values()) rtr.stall(() => this.saveTimeoutId = 0);
484
+ this.adapter?.remove(this.config.key);
485
+ }
486
+ onDestroy() {
487
+ this.state.hydrated && !this.config.disabled && this.adapter?.set(this.config.key, this.payload);
488
+ }
489
+ };
490
+ var PERSIST_MODULE_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 2500, useSnapshot: false };
491
+
492
+ // src/ts/modules/timeTravel.ts
493
+ var TimeTravelModule = class extends BaseReactorModule {
494
+ static moduleName = "timeTravel";
495
+ lastTimestamp = 0;
496
+ playbackTimeoutId = -1;
497
+ constructor(config, rtr) {
498
+ super({ ...TIME_TRAVEL_MODULE_BUILD, ...config }, rtr, { initialState: {}, history: [], currentFrame: 0, paused: true });
499
+ }
500
+ // ===========================================================================
501
+ // THE FOUNDATION & WIRETAP (Passive Recording)
502
+ // ===========================================================================
503
+ wire() {
504
+ this.lastTimestamp = performance.now();
505
+ this.state.set("currentFrame", (v = 0) => clamp(0, v, this.state.history.length), { signal: this.signal, immediate: true });
506
+ this.config.on("paths", this.handlePaths, { signal: this.signal, immediate: true });
507
+ !this.state.paused && this.play();
508
+ }
509
+ onAttach(rtr, rid) {
510
+ rtr.config.referenceTracking = rtr.config.smartCloning = rtr.config.eventTimeStamps = true;
511
+ if (!this.state.history.length || !this.state.initialState[rid]) this.state.initialState[rid] = rtr.snapshot();
512
+ for (const p of this.config.paths ?? wpArr) rtr.on(p, this.record, { signal: this.signal });
513
+ }
514
+ handlePaths({ value: paths = wpArr, oldValue: prevs = wpArr }) {
515
+ for (const rtr of this.rtrs.values()) {
516
+ for (const p of prevs) rtr.off(p, this.record);
517
+ for (const p of paths) rtr.off(p, this.record), rtr.on(p, this.record, { signal: this.signal });
518
+ }
519
+ }
520
+ /** Chronicling the lifecycle of the system, Captures the essence of every mutation wave that bubbles up. */
521
+ record(e, rid = this.rids.get(e.reactor)) {
522
+ if (!this.state.paused) return;
523
+ if (this.state.currentFrame < this.state.history.length) fanout(this.state, "history", this.state.history.slice(0, this.state.currentFrame), { atomic: true });
524
+ if (this.state.history.length >= this.config.maxHistoryLength) fanout(this.state, "history", this.state.history.slice(1), { atomic: true });
525
+ 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 };
526
+ e.rejected && (en.rejected = e.rejected), !e.target.hadKey && (en.hadKey = false), this.state.history.push(en);
527
+ this.state.currentFrame = this.state.history.length;
528
+ this.lastTimestamp = e.timestamp;
529
+ }
530
+ /** Clears timeline history and resets playhead/genesis to the current reactor state. */
531
+ clear() {
532
+ this.pause();
533
+ this.playbackTimeoutId = -1;
534
+ this.state.history.length = this.state.currentFrame = 0;
535
+ this.state.initialState = Object.fromEntries(this.rtrs.entries().map(([rid, rtr]) => [rid, rtr.snapshot()]));
536
+ this.lastTimestamp = performance.now();
537
+ }
538
+ // ===========================================================================
539
+ // THE TIME MACHINE (Manual Controls)
540
+ // ===========================================================================
541
+ /** Instant state reconstruction (Teleport). Glides through deltas natively. */
542
+ jumpTo(index = 0, keepShield = false) {
543
+ this.state.paused = false;
544
+ const target = clamp(0, index, this.state.history.length), forward = target > this.state.currentFrame;
545
+ while (this.state.currentFrame !== target) {
546
+ const e = this.state.history[forward ? this.state.currentFrame : this.state.currentFrame - 1];
547
+ if (!e) break;
548
+ const rtr = this.rtrs.get(e.rid) || this.rtrs.values().next().value;
549
+ if (forward) e.type === "delete" ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.value, rtr.config));
550
+ else e.hadKey === false ? deleteAny(rtr.core, e.path) : setAny(rtr.core, e.path, deepClone(e.oldValue, rtr.config));
551
+ forward ? this.state.currentFrame++ : this.state.currentFrame--;
552
+ if (e.rejected) rtr.log(`[Reactor ${this.name} Module] ${forward ? "Replaying" : "Reversing"} REJECTED intent at "${e.path}"`);
553
+ }
554
+ for (const rtr of this.rtrs.values()) rtr.tick();
555
+ if (!keepShield) this.state.paused = true;
556
+ }
557
+ /** Step through time, Moves the playhead and teleports the state. */
558
+ step(stride = 1, forward = true) {
559
+ if (forward ? this.state.currentFrame >= this.state.history.length : this.state.currentFrame <= 0) return;
560
+ this.pause(), forward ? this.jumpTo(this.state.currentFrame + stride) : this.jumpTo(this.state.currentFrame - stride);
561
+ }
562
+ /** Step back in time, Moves the playhead backward and teleports the state. */
563
+ undo = () => this.step(1, false);
564
+ /** Step forward in time, Restores previously undone actions. */
565
+ redo = () => this.step(1, true);
566
+ // ===========================================================================
567
+ // THE VCR (Automated Playback)
568
+ // ===========================================================================
569
+ /** Core automove engine. Replays or rewinds the "Story" by respecting time gaps. */
570
+ async automove(forward = true) {
571
+ this.state.paused = false;
572
+ while ((forward ? this.state.currentFrame < this.state.history.length : this.state.currentFrame > 0) && !this.state.paused) {
573
+ const idx = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? idx + 1 : idx - 1];
574
+ this.jumpTo(this.state.currentFrame + (forward ? 1 : -1), true);
575
+ if (e?.deltat > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout(() => res(0), Math.min(e.deltat, this.config.maxPlaybackDelay), this.signal));
576
+ }
577
+ this.state.paused = true;
578
+ }
579
+ /** Start chronological re-enactment of the session. */
580
+ play = () => this.automove(true);
581
+ /** Start reverse chronological re-enactment of the session. */
582
+ rewind = () => this.automove(false);
583
+ /** Pauses the live VCR playback. */
584
+ pause = () => (this.state.paused = true, clearTimeout(this.playbackTimeoutId));
585
+ // ===========================================================================
586
+ // TELEMETRY & I/O (Session Import/Export)
587
+ // ===========================================================================
588
+ /** Exports the current session as a JSON string. */
589
+ export(replacer, space) {
590
+ return JSON.stringify(this.state, replacer, space);
591
+ }
592
+ /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
593
+ import(json, reviver) {
594
+ setAny(this.state, "*", JSON.parse(json, reviver));
595
+ this.lastTimestamp = performance.now();
596
+ const resume = !this.state.paused, target = this.state.currentFrame;
597
+ this.state.paused = false;
598
+ for (const [rid, rtr] of this.rtrs) setAny(rtr.core, "*", deepClone(this.state.initialState[rid], rtr.config)), rtr.tick();
599
+ this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
600
+ }
601
+ };
602
+ var TIME_TRAVEL_MODULE_BUILD = { maxPlaybackDelay: 2e3 };
603
+ export {
604
+ AsyncStorageAdapter,
605
+ BaseReactorModule,
606
+ BaseStorageAdapter,
607
+ COOKIE_ADAPTER_BUILD,
608
+ CookieAdapter,
609
+ INDEXED_DB_ADAPTER_BUILD,
610
+ IndexedDBAdapter,
611
+ LocalStorageAdapter,
612
+ MemoryAdapter,
613
+ PERSIST_MODULE_BUILD,
614
+ PersistModule,
615
+ SessionStorageAdapter,
616
+ StorageAdapter,
617
+ TIME_TRAVEL_MODULE_BUILD,
618
+ TimeTravelModule
619
+ };