@flurryx/store 0.8.3 → 1.0.0

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.
package/dist/index.cjs CHANGED
@@ -24,101 +24,652 @@ __export(index_exports, {
24
24
  LazyStore: () => LazyStore,
25
25
  Store: () => Store,
26
26
  clearAllStores: () => clearAllStores,
27
+ cloneValue: () => cloneValue,
27
28
  collectKeyed: () => collectKeyed,
29
+ createCompositeStoreMessageChannel: () => createCompositeStoreMessageChannel,
30
+ createInMemoryStoreMessageChannel: () => createInMemoryStoreMessageChannel,
31
+ createLocalStorageStoreMessageChannel: () => createLocalStorageStoreMessageChannel,
32
+ createSessionStorageStoreMessageChannel: () => createSessionStorageStoreMessageChannel,
33
+ createSnapshotRestorePatch: () => createSnapshotRestorePatch,
34
+ createStorageStoreMessageChannel: () => createStorageStoreMessageChannel,
28
35
  mirrorKey: () => mirrorKey
29
36
  });
30
37
  module.exports = __toCommonJS(index_exports);
31
38
 
32
39
  // src/base-store.ts
33
- var import_core = require("@angular/core");
34
- var import_core2 = require("@flurryx/core");
40
+ var import_core2 = require("@angular/core");
35
41
 
36
- // src/store-registry.ts
37
- var trackedStores = /* @__PURE__ */ new Set();
38
- function trackStore(store) {
39
- trackedStores.add(store);
42
+ // src/store-clone.ts
43
+ function cloneValue(value) {
44
+ if (value !== null && typeof value === "object") {
45
+ const existingClone = cloneReference(value, /* @__PURE__ */ new WeakMap());
46
+ return existingClone;
47
+ }
48
+ return value;
40
49
  }
41
- function clearAllStores() {
42
- for (const store of [...trackedStores]) {
43
- store.clearAll();
50
+ function cloneReference(value, seen) {
51
+ const seenClone = seen.get(value);
52
+ if (seenClone) {
53
+ return seenClone;
54
+ }
55
+ if (value instanceof Date) {
56
+ return new Date(value.getTime());
57
+ }
58
+ if (value instanceof Map) {
59
+ const clonedMap = /* @__PURE__ */ new Map();
60
+ seen.set(value, clonedMap);
61
+ value.forEach((entryValue, key) => {
62
+ clonedMap.set(
63
+ cloneValueWithSeen(key, seen),
64
+ cloneValueWithSeen(entryValue, seen)
65
+ );
66
+ });
67
+ return clonedMap;
68
+ }
69
+ if (value instanceof Set) {
70
+ const clonedSet = /* @__PURE__ */ new Set();
71
+ seen.set(value, clonedSet);
72
+ value.forEach((entryValue) => {
73
+ clonedSet.add(cloneValueWithSeen(entryValue, seen));
74
+ });
75
+ return clonedSet;
76
+ }
77
+ if (Array.isArray(value)) {
78
+ const clonedArray = [];
79
+ seen.set(value, clonedArray);
80
+ value.forEach((item, index) => {
81
+ clonedArray[index] = cloneValueWithSeen(item, seen);
82
+ });
83
+ return clonedArray;
84
+ }
85
+ const clonedObject = Object.create(Object.getPrototypeOf(value));
86
+ seen.set(value, clonedObject);
87
+ Reflect.ownKeys(value).forEach((key) => {
88
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
89
+ if (!descriptor) {
90
+ return;
91
+ }
92
+ if ("value" in descriptor) {
93
+ descriptor.value = cloneValueWithSeen(descriptor.value, seen);
94
+ }
95
+ Object.defineProperty(clonedObject, key, descriptor);
96
+ });
97
+ return clonedObject;
98
+ }
99
+ function cloneValueWithSeen(value, seen) {
100
+ if (value !== null && typeof value === "object") {
101
+ return cloneReference(value, seen);
44
102
  }
103
+ return value;
104
+ }
105
+ function createSnapshotRestorePatch(currentState, snapshotState) {
106
+ const patch = {};
107
+ const keys = /* @__PURE__ */ new Set([
108
+ ...Reflect.ownKeys(currentState),
109
+ ...Reflect.ownKeys(snapshotState)
110
+ ]);
111
+ keys.forEach((key) => {
112
+ if (Object.prototype.hasOwnProperty.call(snapshotState, key)) {
113
+ patch[key] = cloneValue(
114
+ snapshotState[key]
115
+ );
116
+ return;
117
+ }
118
+ patch[key] = void 0;
119
+ });
120
+ return patch;
45
121
  }
46
122
 
47
- // src/base-store.ts
48
- var updateHooksMap = /* @__PURE__ */ new WeakMap();
49
- var BaseStore = class {
50
- constructor(storeEnum) {
51
- this.storeEnum = storeEnum;
52
- this.initializeState();
53
- updateHooksMap.set(this, /* @__PURE__ */ new Map());
54
- trackStore(this);
123
+ // src/store-messages.ts
124
+ var INVALID_HISTORY_INDEX_ERROR = "History index is out of range";
125
+ var INVALID_HISTORY_MESSAGE_ID_ERROR = "History message id is out of range";
126
+ var MESSAGE_NOT_ACKNOWLEDGED_ERROR = "Message was not acknowledged";
127
+
128
+ // src/store-channels.ts
129
+ function serializeStoreMessageChannelValue(value) {
130
+ if (value === void 0) {
131
+ return { __flurryxType: "undefined" };
55
132
  }
56
- signalsState = /* @__PURE__ */ new Map();
57
- get(key) {
58
- return this.signalsState.get(key.toString());
133
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
134
+ return value;
59
135
  }
60
- onUpdate(key, callback) {
61
- const hooks = updateHooksMap.get(this);
62
- if (!hooks.has(key)) {
63
- hooks.set(key, []);
136
+ if (value instanceof Date) {
137
+ return {
138
+ __flurryxType: "date",
139
+ value: value.toISOString()
140
+ };
141
+ }
142
+ if (value instanceof Map) {
143
+ return {
144
+ __flurryxType: "map",
145
+ entries: Array.from(value.entries(), ([key, entryValue]) => [
146
+ serializeStoreMessageChannelValue(key),
147
+ serializeStoreMessageChannelValue(entryValue)
148
+ ])
149
+ };
150
+ }
151
+ if (value instanceof Set) {
152
+ return {
153
+ __flurryxType: "set",
154
+ values: Array.from(
155
+ value.values(),
156
+ (entryValue) => serializeStoreMessageChannelValue(entryValue)
157
+ )
158
+ };
159
+ }
160
+ if (Array.isArray(value)) {
161
+ return {
162
+ __flurryxType: "array",
163
+ values: value.map(
164
+ (entryValue) => serializeStoreMessageChannelValue(entryValue)
165
+ )
166
+ };
167
+ }
168
+ if (typeof value === "object") {
169
+ return {
170
+ __flurryxType: "object",
171
+ entries: Object.entries(value).map(
172
+ ([key, entryValue]) => [
173
+ key,
174
+ serializeStoreMessageChannelValue(entryValue)
175
+ ]
176
+ )
177
+ };
178
+ }
179
+ throw new Error("Store message channel cannot serialize this value");
180
+ }
181
+ function deserializeStoreMessageChannelValue(value) {
182
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
183
+ return value;
184
+ }
185
+ switch (value.__flurryxType) {
186
+ case "undefined":
187
+ return void 0;
188
+ case "date":
189
+ return new Date(value.value);
190
+ case "array":
191
+ return value.values.map(
192
+ (entryValue) => deserializeStoreMessageChannelValue(entryValue)
193
+ );
194
+ case "set":
195
+ return new Set(
196
+ value.values.map(
197
+ (entryValue) => deserializeStoreMessageChannelValue(entryValue)
198
+ )
199
+ );
200
+ case "map":
201
+ return new Map(
202
+ value.entries.map(([key, entryValue]) => [
203
+ deserializeStoreMessageChannelValue(key),
204
+ deserializeStoreMessageChannelValue(entryValue)
205
+ ])
206
+ );
207
+ case "object": {
208
+ const result = {};
209
+ value.entries.forEach(([key, entryValue]) => {
210
+ result[key] = deserializeStoreMessageChannelValue(entryValue);
211
+ });
212
+ return result;
64
213
  }
65
- hooks.get(key).push(
66
- callback
67
- );
68
- return () => {
69
- const hooksMap = hooks.get(key);
70
- if (!hooksMap) {
214
+ }
215
+ }
216
+ function defaultSerializeStoreMessageChannelState(state) {
217
+ return JSON.stringify(serializeStoreMessageChannelValue(state));
218
+ }
219
+ function defaultDeserializeStoreMessageChannelState(value) {
220
+ return deserializeStoreMessageChannelValue(
221
+ JSON.parse(value)
222
+ );
223
+ }
224
+ function normalizeStoreMessageChannelState(state) {
225
+ const maxId = state.messages.reduce(
226
+ (currentMax, entry) => Math.max(currentMax, entry.id),
227
+ 0
228
+ );
229
+ return {
230
+ nextId: Math.max(state.nextId, maxId + 1),
231
+ messages: state.messages.map((entry) => cloneValue(entry))
232
+ };
233
+ }
234
+ function createInitialStoreMessageRecord(id, message, clock = Date.now) {
235
+ return {
236
+ id,
237
+ message: cloneValue(message),
238
+ status: "pending",
239
+ attempts: 0,
240
+ createdAt: clock(),
241
+ lastAttemptedAt: null,
242
+ acknowledgedAt: null,
243
+ error: null
244
+ };
245
+ }
246
+ function isQuotaExceededError(error) {
247
+ return error instanceof DOMException && (error.code === 22 || error.code === 1014 || error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED");
248
+ }
249
+ function resolveGlobalStorage(name) {
250
+ const storage = globalThis[name];
251
+ if (!storage) {
252
+ throw new Error(`${name} is not available in this environment`);
253
+ }
254
+ return storage;
255
+ }
256
+ function createInMemoryStoreMessageChannel() {
257
+ let messages = [];
258
+ let nextId = 1;
259
+ return {
260
+ publish(message) {
261
+ const record = createInitialStoreMessageRecord(nextId++, message);
262
+ messages = [...messages, record];
263
+ return cloneValue(record);
264
+ },
265
+ getMessage(id) {
266
+ const record = messages.find((entry) => entry.id === id);
267
+ return record ? cloneValue(record) : void 0;
268
+ },
269
+ getMessages() {
270
+ return messages.map((entry) => cloneValue(entry));
271
+ },
272
+ saveMessage(entry) {
273
+ const record = cloneValue(entry);
274
+ const existingIndex = messages.findIndex(
275
+ (candidate) => candidate.id === record.id
276
+ );
277
+ if (existingIndex === -1) {
278
+ messages = [...messages, record];
279
+ } else {
280
+ messages = messages.map((c, i) => i === existingIndex ? record : c);
281
+ }
282
+ nextId = Math.max(nextId, record.id + 1);
283
+ }
284
+ };
285
+ }
286
+ function createStorageStoreMessageChannel(options) {
287
+ const serialize = options.serialize ?? defaultSerializeStoreMessageChannelState;
288
+ const deserialize = options.deserialize ?? defaultDeserializeStoreMessageChannelState;
289
+ function readState() {
290
+ const rawState = options.storage.getItem(options.storageKey);
291
+ if (rawState === null) {
292
+ return { nextId: 1, messages: [] };
293
+ }
294
+ try {
295
+ return normalizeStoreMessageChannelState(deserialize(rawState));
296
+ } catch {
297
+ return { nextId: 1, messages: [] };
298
+ }
299
+ }
300
+ function writeState(state) {
301
+ const normalized = normalizeStoreMessageChannelState(state);
302
+ try {
303
+ options.storage.setItem(options.storageKey, serialize(normalized));
304
+ } catch (error) {
305
+ if (!isQuotaExceededError(error) || normalized.messages.length === 0) {
306
+ throw error;
307
+ }
308
+ let remaining = [...normalized.messages];
309
+ while (remaining.length > 0) {
310
+ remaining = remaining.slice(1);
311
+ try {
312
+ options.storage.setItem(
313
+ options.storageKey,
314
+ serialize({ nextId: normalized.nextId, messages: remaining })
315
+ );
316
+ return;
317
+ } catch (retryError) {
318
+ if (!isQuotaExceededError(retryError)) {
319
+ throw retryError;
320
+ }
321
+ }
322
+ }
323
+ options.storage.setItem(
324
+ options.storageKey,
325
+ serialize({ nextId: normalized.nextId, messages: [] })
326
+ );
327
+ }
328
+ }
329
+ return {
330
+ publish(message) {
331
+ const state = readState();
332
+ const record = createInitialStoreMessageRecord(state.nextId, message);
333
+ writeState({
334
+ nextId: state.nextId + 1,
335
+ messages: [...state.messages, record]
336
+ });
337
+ return cloneValue(record);
338
+ },
339
+ getMessage(id) {
340
+ const record = readState().messages.find((entry) => entry.id === id);
341
+ return record ? cloneValue(record) : void 0;
342
+ },
343
+ getMessages() {
344
+ return readState().messages.map((entry) => cloneValue(entry));
345
+ },
346
+ saveMessage(entry) {
347
+ const state = readState();
348
+ const record = cloneValue(entry);
349
+ const existingIndex = state.messages.findIndex(
350
+ (candidate) => candidate.id === record.id
351
+ );
352
+ if (existingIndex === -1) {
353
+ writeState({
354
+ nextId: Math.max(state.nextId, record.id + 1),
355
+ messages: [...state.messages, record]
356
+ });
71
357
  return;
72
358
  }
73
- const index = hooksMap.indexOf(
74
- callback
359
+ const nextMessages = state.messages.map(
360
+ (c, i) => i === existingIndex ? record : c
75
361
  );
76
- if (index > -1) {
77
- hooksMap.splice(index, 1);
362
+ writeState({
363
+ nextId: Math.max(state.nextId, record.id + 1),
364
+ messages: nextMessages
365
+ });
366
+ }
367
+ };
368
+ }
369
+ function createLocalStorageStoreMessageChannel(options) {
370
+ return createStorageStoreMessageChannel({
371
+ ...options,
372
+ storage: options.storage ?? resolveGlobalStorage("localStorage")
373
+ });
374
+ }
375
+ function createSessionStorageStoreMessageChannel(options) {
376
+ return createStorageStoreMessageChannel({
377
+ ...options,
378
+ storage: options.storage ?? resolveGlobalStorage("sessionStorage")
379
+ });
380
+ }
381
+ function createCompositeStoreMessageChannel(options) {
382
+ if (options.channels.length === 0) {
383
+ throw new Error(
384
+ "createCompositeStoreMessageChannel: 'channels' option must contain at least one channel"
385
+ );
386
+ }
387
+ const primaryChannel = options.channels[0];
388
+ const replicaChannels = options.channels.slice(1);
389
+ return {
390
+ publish(message) {
391
+ const record = primaryChannel.publish(message);
392
+ replicaChannels.forEach((channel) => {
393
+ channel.saveMessage(record);
394
+ });
395
+ return cloneValue(record);
396
+ },
397
+ getMessage(id) {
398
+ const record = primaryChannel.getMessage(id);
399
+ return record ? cloneValue(record) : void 0;
400
+ },
401
+ getMessages() {
402
+ return primaryChannel.getMessages().map((record) => cloneValue(record));
403
+ },
404
+ saveMessage(entry) {
405
+ primaryChannel.saveMessage(entry);
406
+ replicaChannels.forEach((channel) => {
407
+ channel.saveMessage(entry);
408
+ });
409
+ }
410
+ };
411
+ }
412
+
413
+ // src/store-replay.ts
414
+ function messageAffectsKey(message, key) {
415
+ if (message.type === "clearAll") {
416
+ return true;
417
+ }
418
+ return "key" in message && message.key === key;
419
+ }
420
+ function toDeadLetterEntry(record) {
421
+ return {
422
+ id: record.id,
423
+ message: cloneValue(record.message),
424
+ attempts: record.attempts,
425
+ error: record.error ?? MESSAGE_NOT_ACKNOWLEDGED_ERROR,
426
+ failedAt: record.lastAttemptedAt ?? record.createdAt
427
+ };
428
+ }
429
+ function createStoreHistory(config) {
430
+ const messageChannel = config.channel ?? createInMemoryStoreMessageChannel();
431
+ const clock = config.clock ?? Date.now;
432
+ let history = [
433
+ {
434
+ id: null,
435
+ index: 0,
436
+ message: null,
437
+ snapshot: config.captureSnapshot(),
438
+ acknowledgedAt: null
439
+ }
440
+ ];
441
+ let currentIndex = 0;
442
+ function recordSnapshot(record) {
443
+ const nextIndex = history.length;
444
+ history = [
445
+ ...history,
446
+ {
447
+ id: record.id,
448
+ index: nextIndex,
449
+ message: cloneValue(record.message),
450
+ snapshot: config.captureSnapshot(),
451
+ acknowledgedAt: record.acknowledgedAt
78
452
  }
79
- };
453
+ ];
454
+ currentIndex = nextIndex;
80
455
  }
81
- update(key, newState) {
82
- const currentState = this.signalsState.get(key.toString());
83
- if (!currentState) {
456
+ function truncateFutureHistory() {
457
+ if (currentIndex === history.length - 1) {
84
458
  return;
85
459
  }
86
- const previousState = currentState();
87
- currentState.update((state) => ({
88
- ...state,
89
- ...newState
90
- }));
91
- const updatedState = currentState();
92
- this.notifyUpdateHooks(key, updatedState, previousState);
460
+ history = history.slice(0, currentIndex + 1);
93
461
  }
94
- clearAll() {
95
- Object.keys(this.storeEnum).forEach((key) => {
96
- this.clear(key);
462
+ function ensureIndexInRange(index) {
463
+ if (!Number.isInteger(index) || index < 0 || index >= history.length) {
464
+ throw new Error(INVALID_HISTORY_INDEX_ERROR);
465
+ }
466
+ }
467
+ function travelTo(index) {
468
+ ensureIndexInRange(index);
469
+ config.applySnapshot(history[index].snapshot);
470
+ currentIndex = index;
471
+ }
472
+ function undo() {
473
+ if (currentIndex === 0) {
474
+ return false;
475
+ }
476
+ travelTo(currentIndex - 1);
477
+ return true;
478
+ }
479
+ function redo() {
480
+ if (currentIndex >= history.length - 1) {
481
+ return false;
482
+ }
483
+ travelTo(currentIndex + 1);
484
+ return true;
485
+ }
486
+ function getErrorMessage(error) {
487
+ if (error instanceof Error && error.message) {
488
+ return error.message;
489
+ }
490
+ return MESSAGE_NOT_ACKNOWLEDGED_ERROR;
491
+ }
492
+ function persistMessageAttempt(record, status, error, attemptedAt) {
493
+ const nextRecord = {
494
+ ...record,
495
+ message: cloneValue(record.message),
496
+ status,
497
+ attempts: record.attempts + 1,
498
+ lastAttemptedAt: attemptedAt,
499
+ acknowledgedAt: status === "acknowledged" ? attemptedAt : record.acknowledgedAt,
500
+ error
501
+ };
502
+ messageChannel.saveMessage(nextRecord);
503
+ return nextRecord;
504
+ }
505
+ function consumeRecord(record, options) {
506
+ const clonedMessage = cloneValue(record.message);
507
+ const attemptedAt = clock();
508
+ try {
509
+ const acknowledged = config.applyMessage(clonedMessage);
510
+ if (!acknowledged) {
511
+ throw new Error(MESSAGE_NOT_ACKNOWLEDGED_ERROR);
512
+ }
513
+ const acknowledgedRecord = persistMessageAttempt(
514
+ {
515
+ ...record,
516
+ message: clonedMessage
517
+ },
518
+ "acknowledged",
519
+ null,
520
+ attemptedAt
521
+ );
522
+ if (options?.recordHistory !== false) {
523
+ truncateFutureHistory();
524
+ recordSnapshot(acknowledgedRecord);
525
+ }
526
+ return true;
527
+ } catch (error) {
528
+ persistMessageAttempt(
529
+ {
530
+ ...record,
531
+ message: clonedMessage
532
+ },
533
+ "dead-letter",
534
+ getErrorMessage(error),
535
+ attemptedAt
536
+ );
537
+ return false;
538
+ }
539
+ }
540
+ function resolveReplayRecords(ids) {
541
+ return ids.map((id) => {
542
+ if (!Number.isInteger(id) || id < 1) {
543
+ throw new Error(INVALID_HISTORY_MESSAGE_ID_ERROR);
544
+ }
545
+ const record = messageChannel.getMessage(id);
546
+ if (!record) {
547
+ throw new Error(INVALID_HISTORY_MESSAGE_ID_ERROR);
548
+ }
549
+ return cloneValue(record);
97
550
  });
98
551
  }
99
- clear(key) {
100
- const currentState = this.signalsState.get(key.toString());
101
- if (!currentState) {
102
- return;
552
+ function replayByIds(input) {
553
+ const ids = Array.isArray(input) ? input : [input];
554
+ const records = resolveReplayRecords(ids);
555
+ let acknowledgedCount = 0;
556
+ records.forEach((record) => {
557
+ if (consumeRecord(record)) {
558
+ acknowledgedCount += 1;
559
+ }
560
+ });
561
+ return acknowledgedCount;
562
+ }
563
+ function replayDeadLetter(id) {
564
+ const record = messageChannel.getMessage(id);
565
+ if (!record || record.status !== "dead-letter") {
566
+ return false;
103
567
  }
104
- const previousState = currentState();
105
- const _typedKey = key;
106
- currentState.set({
107
- data: void 0,
108
- isLoading: false,
109
- status: void 0,
110
- errors: void 0
568
+ return consumeRecord(record);
569
+ }
570
+ function replayDeadLetters() {
571
+ const ids = messageChannel.getMessages().filter((record) => record.status === "dead-letter").map((record) => record.id);
572
+ let acknowledgedCount = 0;
573
+ ids.forEach((id) => {
574
+ if (replayDeadLetter(id)) {
575
+ acknowledgedCount += 1;
576
+ }
111
577
  });
112
- const nextState = currentState();
113
- this.notifyUpdateHooks(key, nextState, previousState);
578
+ return acknowledgedCount;
114
579
  }
115
- startLoading(key) {
116
- const currentState = this.signalsState.get(key.toString());
117
- if (!currentState) {
118
- return;
580
+ return {
581
+ publish(message) {
582
+ const record = messageChannel.publish(message);
583
+ return consumeRecord(record);
584
+ },
585
+ replay(input) {
586
+ return replayByIds(input);
587
+ },
588
+ travelTo,
589
+ undo,
590
+ redo,
591
+ getHistory(key) {
592
+ if (key === void 0) {
593
+ return history.map((entry) => cloneValue(entry));
594
+ }
595
+ return history.filter((entry) => {
596
+ if (entry.message === null) {
597
+ return true;
598
+ }
599
+ return messageAffectsKey(entry.message, key);
600
+ }).map((entry) => cloneValue(entry));
601
+ },
602
+ getMessages(key) {
603
+ const records = messageChannel.getMessages();
604
+ if (key === void 0) {
605
+ return records.map((record) => cloneValue(record));
606
+ }
607
+ return records.filter((record) => messageAffectsKey(record.message, key)).map((record) => cloneValue(record));
608
+ },
609
+ getDeadLetters() {
610
+ return messageChannel.getMessages().filter((record) => record.status === "dead-letter").map((record) => toDeadLetterEntry(record));
611
+ },
612
+ replayDeadLetter,
613
+ replayDeadLetters,
614
+ getCurrentIndex() {
615
+ return currentIndex;
616
+ }
617
+ };
618
+ }
619
+
620
+ // src/store-registry.ts
621
+ var trackedStores = /* @__PURE__ */ new Set();
622
+ function trackStore(store) {
623
+ trackedStores.add(store);
624
+ }
625
+ function clearAllStores() {
626
+ for (const store of [...trackedStores]) {
627
+ store.clearAll();
628
+ }
629
+ }
630
+
631
+ // src/store-message-consumer.ts
632
+ var import_core = require("@flurryx/core");
633
+ function createDefaultState() {
634
+ return {
635
+ data: void 0,
636
+ isLoading: false,
637
+ status: void 0,
638
+ errors: void 0
639
+ };
640
+ }
641
+ function createStoreMessageConsumer(signals, notifier) {
642
+ function applyUpdate(key, newState, notify = true) {
643
+ const sig = signals.getOrCreate(key);
644
+ const previousState = sig();
645
+ sig.update((state) => ({ ...state, ...newState }));
646
+ if (notify) {
647
+ const updatedState = sig();
648
+ notifier.notify(key, updatedState, previousState);
649
+ }
650
+ return true;
651
+ }
652
+ function applyClear(key) {
653
+ const sig = signals.getOrCreate(key);
654
+ const previousState = sig();
655
+ sig.set(createDefaultState());
656
+ const nextState = sig();
657
+ notifier.notify(key, nextState, previousState);
658
+ return true;
659
+ }
660
+ function applyClearAll() {
661
+ const keys = Array.from(signals.getAllKeys());
662
+ if (keys.length === 0) {
663
+ return false;
119
664
  }
120
- const _typedKey = key;
121
- currentState.update(
665
+ keys.forEach((key) => {
666
+ applyClear(key);
667
+ });
668
+ return true;
669
+ }
670
+ function applyStartLoading(key) {
671
+ const sig = signals.getOrCreate(key);
672
+ sig.update(
122
673
  (state) => ({
123
674
  ...state,
124
675
  status: void 0,
@@ -126,61 +677,44 @@ var BaseStore = class {
126
677
  errors: void 0
127
678
  })
128
679
  );
680
+ return true;
129
681
  }
130
- stopLoading(key) {
131
- const currentState = this.signalsState.get(key.toString());
132
- if (!currentState) {
133
- return;
134
- }
135
- const _typedKey = key;
136
- currentState.update(
682
+ function applyStopLoading(key) {
683
+ const sig = signals.getOrCreate(key);
684
+ sig.update(
137
685
  (state) => ({
138
686
  ...state,
139
687
  isLoading: false
140
688
  })
141
689
  );
690
+ return true;
142
691
  }
143
- updateKeyedOne(key, resourceKey, entity) {
144
- const currentState = this.signalsState.get(key.toString());
145
- if (!currentState) {
146
- return;
147
- }
148
- const state = currentState();
149
- const data = (0, import_core2.isKeyedResourceData)(state.data) ? state.data : (0, import_core2.createKeyedResourceData)();
692
+ function applyUpdateKeyedOne(key, resourceKey, entity) {
693
+ const sig = signals.getOrCreate(key);
694
+ const state = sig();
695
+ const data = (0, import_core.isKeyedResourceData)(state.data) ? state.data : (0, import_core.createKeyedResourceData)();
150
696
  const nextErrors = { ...data.errors };
151
697
  delete nextErrors[resourceKey];
152
698
  const nextData = {
153
699
  ...data,
154
- entities: {
155
- ...data.entities,
156
- [resourceKey]: entity
157
- },
158
- isLoading: {
159
- ...data.isLoading,
160
- [resourceKey]: false
161
- },
162
- status: {
163
- ...data.status,
164
- [resourceKey]: "Success"
165
- },
700
+ entities: { ...data.entities, [resourceKey]: entity },
701
+ isLoading: { ...data.isLoading, [resourceKey]: false },
702
+ status: { ...data.status, [resourceKey]: "Success" },
166
703
  errors: nextErrors
167
704
  };
168
- this.update(key, {
705
+ return applyUpdate(key, {
169
706
  data: nextData,
170
- isLoading: (0, import_core2.isAnyKeyLoading)(nextData.isLoading),
707
+ isLoading: (0, import_core.isAnyKeyLoading)(nextData.isLoading),
171
708
  status: void 0,
172
709
  errors: void 0
173
710
  });
174
711
  }
175
- clearKeyedOne(key, resourceKey) {
176
- const currentState = this.signalsState.get(key.toString());
177
- if (!currentState) {
178
- return;
179
- }
180
- const previousState = currentState();
712
+ function applyClearKeyedOne(key, resourceKey) {
713
+ const sig = signals.getOrCreate(key);
714
+ const previousState = sig();
181
715
  const state = previousState;
182
- if (!(0, import_core2.isKeyedResourceData)(state.data)) {
183
- return;
716
+ if (!(0, import_core.isKeyedResourceData)(state.data)) {
717
+ return true;
184
718
  }
185
719
  const data = state.data;
186
720
  const nextEntities = { ...data.entities };
@@ -198,29 +732,24 @@ var BaseStore = class {
198
732
  status: nextStatus,
199
733
  errors: nextErrors
200
734
  };
201
- const _typedKey = key;
202
- currentState.update(
735
+ sig.update(
203
736
  (prev) => ({
204
737
  ...prev,
205
738
  data: nextData,
206
739
  status: void 0,
207
- isLoading: (0, import_core2.isAnyKeyLoading)(nextIsLoading),
740
+ isLoading: (0, import_core.isAnyKeyLoading)(nextIsLoading),
208
741
  errors: void 0
209
742
  })
210
743
  );
211
- const updatedState = currentState();
212
- this.notifyUpdateHooks(key, updatedState, previousState);
744
+ const updatedState = sig();
745
+ notifier.notify(key, updatedState, previousState);
746
+ return true;
213
747
  }
214
- startKeyedLoading(key, resourceKey) {
215
- const currentState = this.signalsState.get(key.toString());
216
- if (!currentState) {
217
- return;
218
- }
219
- const _typedKey = key;
220
- const state = currentState();
221
- if (!(0, import_core2.isKeyedResourceData)(state.data)) {
222
- this.startLoading(key);
223
- return;
748
+ function applyStartKeyedLoading(key, resourceKey) {
749
+ const sig = signals.getOrCreate(key);
750
+ const state = sig();
751
+ if (!(0, import_core.isKeyedResourceData)(state.data)) {
752
+ return applyStartLoading(key);
224
753
  }
225
754
  const previousState = state;
226
755
  const data = state.data;
@@ -228,13 +757,9 @@ var BaseStore = class {
228
757
  ...data.isLoading,
229
758
  [resourceKey]: true
230
759
  };
231
- const nextStatus = {
232
- ...data.status
233
- };
760
+ const nextStatus = { ...data.status };
234
761
  delete nextStatus[resourceKey];
235
- const nextErrors = {
236
- ...data.errors
237
- };
762
+ const nextErrors = { ...data.errors };
238
763
  delete nextErrors[resourceKey];
239
764
  const nextData = {
240
765
  ...data,
@@ -242,17 +767,286 @@ var BaseStore = class {
242
767
  status: nextStatus,
243
768
  errors: nextErrors
244
769
  };
245
- currentState.update(
770
+ sig.update(
246
771
  (previous) => ({
247
772
  ...previous,
248
773
  data: nextData,
249
774
  status: void 0,
250
- isLoading: (0, import_core2.isAnyKeyLoading)(nextIsLoading),
775
+ isLoading: (0, import_core.isAnyKeyLoading)(nextIsLoading),
251
776
  errors: void 0
252
777
  })
253
778
  );
254
- const updatedState = currentState();
255
- this.notifyUpdateHooks(key, updatedState, previousState);
779
+ const updatedState = sig();
780
+ notifier.notify(key, updatedState, previousState);
781
+ return true;
782
+ }
783
+ function applyMessage(message) {
784
+ switch (message.type) {
785
+ case "update":
786
+ return applyUpdate(message.key, cloneValue(message.state));
787
+ case "clear":
788
+ return applyClear(message.key);
789
+ case "clearAll":
790
+ return applyClearAll();
791
+ case "startLoading":
792
+ return applyStartLoading(message.key);
793
+ case "stopLoading":
794
+ return applyStopLoading(message.key);
795
+ case "updateKeyedOne":
796
+ return applyUpdateKeyedOne(
797
+ message.key,
798
+ message.resourceKey,
799
+ cloneValue(message.entity)
800
+ );
801
+ case "clearKeyedOne":
802
+ return applyClearKeyedOne(message.key, message.resourceKey);
803
+ case "startKeyedLoading":
804
+ return applyStartKeyedLoading(message.key, message.resourceKey);
805
+ }
806
+ }
807
+ function applySnapshot(snapshot) {
808
+ const keys = /* @__PURE__ */ new Set([
809
+ ...Array.from(signals.getAllKeys()),
810
+ ...Object.keys(snapshot)
811
+ ]);
812
+ keys.forEach((rawKey) => {
813
+ const key = rawKey;
814
+ const sig = signals.getOrCreate(key);
815
+ const snapshotState = snapshot[key] ?? createDefaultState();
816
+ applyUpdate(key, createSnapshotRestorePatch(sig(), snapshotState), true);
817
+ });
818
+ }
819
+ function captureSnapshot() {
820
+ const entries = Array.from(signals.getAllKeys()).map((key) => [
821
+ key,
822
+ cloneValue(signals.getOrCreate(key)())
823
+ ]);
824
+ return Object.fromEntries(entries);
825
+ }
826
+ return {
827
+ applyMessage,
828
+ applySnapshot,
829
+ createSnapshot: captureSnapshot
830
+ };
831
+ }
832
+ function createUpdateMessage(key, state) {
833
+ return { type: "update", key, state };
834
+ }
835
+ function createClearMessage(key) {
836
+ return { type: "clear", key };
837
+ }
838
+ function createClearAllMessage() {
839
+ return { type: "clearAll" };
840
+ }
841
+ function createStartLoadingMessage(key) {
842
+ return { type: "startLoading", key };
843
+ }
844
+ function createStopLoadingMessage(key) {
845
+ return { type: "stopLoading", key };
846
+ }
847
+ function createUpdateKeyedOneMessage(key, resourceKey, entity) {
848
+ return {
849
+ type: "updateKeyedOne",
850
+ key,
851
+ resourceKey,
852
+ entity
853
+ };
854
+ }
855
+ function createClearKeyedOneMessage(key, resourceKey) {
856
+ return {
857
+ type: "clearKeyedOne",
858
+ key,
859
+ resourceKey
860
+ };
861
+ }
862
+ function createStartKeyedLoadingMessage(key, resourceKey) {
863
+ return {
864
+ type: "startKeyedLoading",
865
+ key,
866
+ resourceKey
867
+ };
868
+ }
869
+
870
+ // src/base-store.ts
871
+ var updateHooksMap = /* @__PURE__ */ new WeakMap();
872
+ var BaseStore = class {
873
+ constructor(storeEnum, options) {
874
+ this.storeEnum = storeEnum;
875
+ this.storeKeys = Object.keys(storeEnum);
876
+ this.initializeState();
877
+ updateHooksMap.set(this, /* @__PURE__ */ new Map());
878
+ const consumer = createStoreMessageConsumer(
879
+ {
880
+ getOrCreate: (key) => this.signalsState.get(key),
881
+ getAllKeys: () => this.storeKeys
882
+ },
883
+ {
884
+ notify: (key, next, prev) => this.notifyUpdateHooks(key, next, prev)
885
+ }
886
+ );
887
+ this.history = createStoreHistory({
888
+ captureSnapshot: () => consumer.createSnapshot(),
889
+ applySnapshot: (snapshot) => consumer.applySnapshot(snapshot),
890
+ applyMessage: (message) => consumer.applyMessage(message),
891
+ channel: options?.channel
892
+ });
893
+ trackStore(this);
894
+ }
895
+ signalsState = /* @__PURE__ */ new Map();
896
+ storeKeys;
897
+ history;
898
+ /** @inheritDoc */
899
+ travelTo = (index) => this.history.travelTo(index);
900
+ /** @inheritDoc */
901
+ undo = () => this.history.undo();
902
+ /** @inheritDoc */
903
+ redo = () => this.history.redo();
904
+ /** @inheritDoc */
905
+ getDeadLetters = () => this.history.getDeadLetters();
906
+ /** @inheritDoc */
907
+ replayDeadLetter = (id) => this.history.replayDeadLetter(id);
908
+ /** @inheritDoc */
909
+ replayDeadLetters = () => this.history.replayDeadLetters();
910
+ /** @inheritDoc */
911
+ getCurrentIndex = () => this.history.getCurrentIndex();
912
+ replay(idOrIds) {
913
+ if (Array.isArray(idOrIds)) {
914
+ return this.history.replay(idOrIds);
915
+ }
916
+ return this.history.replay(idOrIds);
917
+ }
918
+ getHistory(key) {
919
+ if (key === void 0) {
920
+ return this.history.getHistory();
921
+ }
922
+ return this.history.getHistory(key);
923
+ }
924
+ getMessages(key) {
925
+ if (key === void 0) {
926
+ return this.history.getMessages();
927
+ }
928
+ return this.history.getMessages(key);
929
+ }
930
+ /**
931
+ * Returns a **read-only** `Signal` for the given store slot.
932
+ *
933
+ * @param key - The slot name to read.
934
+ * @returns A `Signal` wrapping the slot's current {@link ResourceState}.
935
+ */
936
+ get(key) {
937
+ return this.signalsState.get(key.toString());
938
+ }
939
+ /**
940
+ * Registers a callback fired after every `update` or `clear` on the given slot.
941
+ *
942
+ * @param key - The slot to watch.
943
+ * @param callback - Receives the new state and the previous state.
944
+ * @returns A cleanup function that removes the listener when called.
945
+ */
946
+ onUpdate(key, callback) {
947
+ const hooks = updateHooksMap.get(this);
948
+ if (!hooks.has(key)) {
949
+ hooks.set(key, []);
950
+ }
951
+ hooks.get(key).push(
952
+ callback
953
+ );
954
+ return () => {
955
+ const hooksMap = hooks.get(key);
956
+ if (!hooksMap) {
957
+ return;
958
+ }
959
+ const index = hooksMap.indexOf(
960
+ callback
961
+ );
962
+ if (index > -1) {
963
+ hooksMap.splice(index, 1);
964
+ }
965
+ };
966
+ }
967
+ /**
968
+ * Partially updates a slot by merging `newState` into the current value (immutable spread).
969
+ *
970
+ * @param key - The slot to update.
971
+ * @param newState - Partial state to merge (e.g. `{ data: newData, status: 'Success' }`).
972
+ */
973
+ update(key, newState) {
974
+ this.history.publish(
975
+ createUpdateMessage(key, cloneValue(newState))
976
+ );
977
+ }
978
+ /** Resets every slot in this store to its initial idle state. */
979
+ clearAll() {
980
+ this.history.publish(createClearAllMessage());
981
+ }
982
+ /**
983
+ * Resets a single slot to `{ data: undefined, isLoading: false, status: undefined, errors: undefined }`.
984
+ *
985
+ * @param key - The slot to clear.
986
+ */
987
+ clear(key) {
988
+ this.history.publish(createClearMessage(key));
989
+ }
990
+ /**
991
+ * Marks a slot as loading: sets `isLoading: true` and clears `status` and `errors`.
992
+ *
993
+ * @param key - The slot to mark as loading.
994
+ */
995
+ startLoading(key) {
996
+ this.history.publish(createStartLoadingMessage(key));
997
+ }
998
+ /**
999
+ * Marks a slot as no longer loading: sets `isLoading: false`.
1000
+ * Does **not** clear `status` or `errors`.
1001
+ *
1002
+ * @param key - The slot to stop loading.
1003
+ */
1004
+ stopLoading(key) {
1005
+ this.history.publish(createStopLoadingMessage(key));
1006
+ }
1007
+ /**
1008
+ * Merges a single entity into a {@link KeyedResourceData} slot.
1009
+ * Sets its status to `'Success'` and clears per-key errors.
1010
+ * The top-level `isLoading` is recalculated based on remaining loading keys.
1011
+ *
1012
+ * @param key - The keyed slot name.
1013
+ * @param resourceKey - The entity identifier (e.g. `'inv-123'`).
1014
+ * @param entity - The entity value to store.
1015
+ */
1016
+ updateKeyedOne(key, resourceKey, entity) {
1017
+ this.history.publish(
1018
+ createUpdateKeyedOneMessage(
1019
+ key,
1020
+ resourceKey,
1021
+ cloneValue(entity)
1022
+ )
1023
+ );
1024
+ }
1025
+ /**
1026
+ * Removes a single entity from a {@link KeyedResourceData} slot,
1027
+ * including its loading flag, status, and errors.
1028
+ * Recalculates the top-level `isLoading` from the remaining keys.
1029
+ *
1030
+ * @param key - The keyed slot name.
1031
+ * @param resourceKey - The entity identifier to remove.
1032
+ */
1033
+ clearKeyedOne(key, resourceKey) {
1034
+ this.history.publish(
1035
+ createClearKeyedOneMessage(key, resourceKey)
1036
+ );
1037
+ }
1038
+ /**
1039
+ * Marks a single entity within a keyed slot as loading.
1040
+ * Clears its status and errors. If the slot data is not yet a {@link KeyedResourceData},
1041
+ * falls back to `startLoading(key)`.
1042
+ *
1043
+ * @param key - The keyed slot name.
1044
+ * @param resourceKey - The entity identifier to mark as loading.
1045
+ */
1046
+ startKeyedLoading(key, resourceKey) {
1047
+ this.history.publish(
1048
+ createStartKeyedLoadingMessage(key, resourceKey)
1049
+ );
256
1050
  }
257
1051
  notifyUpdateHooks(key, nextState, previousState) {
258
1052
  const hooks = updateHooksMap.get(this);
@@ -260,25 +1054,34 @@ var BaseStore = class {
260
1054
  if (!keyHooks) {
261
1055
  return;
262
1056
  }
263
- keyHooks.forEach(
264
- (hook) => hook(
265
- nextState,
266
- previousState
267
- )
268
- );
1057
+ const errors = [];
1058
+ keyHooks.forEach((hook) => {
1059
+ try {
1060
+ hook(
1061
+ nextState,
1062
+ previousState
1063
+ );
1064
+ } catch (error) {
1065
+ errors.push(error);
1066
+ }
1067
+ });
1068
+ if (errors.length > 0) {
1069
+ queueMicrotask(() => {
1070
+ if (errors.length === 1) {
1071
+ throw errors[0];
1072
+ }
1073
+ throw new AggregateError(
1074
+ errors,
1075
+ `${errors.length} onUpdate hooks threw for key "${String(key)}"`
1076
+ );
1077
+ });
1078
+ }
269
1079
  }
270
1080
  initializeState() {
271
- Object.keys(this.storeEnum).forEach((key) => {
272
- const _typedKey = key;
273
- const initialState = {
274
- data: void 0,
275
- isLoading: false,
276
- status: void 0,
277
- errors: void 0
278
- };
1081
+ this.storeKeys.forEach((key) => {
279
1082
  this.signalsState.set(
280
- _typedKey,
281
- (0, import_core.signal)(initialState)
1083
+ key,
1084
+ (0, import_core2.signal)(createDefaultState())
282
1085
  );
283
1086
  });
284
1087
  }
@@ -286,19 +1089,58 @@ var BaseStore = class {
286
1089
 
287
1090
  // src/lazy-store.ts
288
1091
  var import_core3 = require("@angular/core");
289
- var import_core4 = require("@flurryx/core");
290
- function createDefaultState() {
291
- return {
292
- data: void 0,
293
- isLoading: false,
294
- status: void 0,
295
- errors: void 0
296
- };
297
- }
298
1092
  var LazyStore = class {
299
1093
  signals = /* @__PURE__ */ new Map();
300
1094
  hooks = /* @__PURE__ */ new Map();
301
- constructor() {
1095
+ history;
1096
+ /** @inheritDoc */
1097
+ travelTo = (index) => this.history.travelTo(index);
1098
+ /** @inheritDoc */
1099
+ undo = () => this.history.undo();
1100
+ /** @inheritDoc */
1101
+ redo = () => this.history.redo();
1102
+ getMessages(key) {
1103
+ if (key === void 0) {
1104
+ return this.history.getMessages();
1105
+ }
1106
+ return this.history.getMessages(key);
1107
+ }
1108
+ /** @inheritDoc */
1109
+ getDeadLetters = () => this.history.getDeadLetters();
1110
+ /** @inheritDoc */
1111
+ replayDeadLetter = (id) => this.history.replayDeadLetter(id);
1112
+ /** @inheritDoc */
1113
+ replayDeadLetters = () => this.history.replayDeadLetters();
1114
+ /** @inheritDoc */
1115
+ getCurrentIndex = () => this.history.getCurrentIndex();
1116
+ replay(idOrIds) {
1117
+ if (Array.isArray(idOrIds)) {
1118
+ return this.history.replay(idOrIds);
1119
+ }
1120
+ return this.history.replay(idOrIds);
1121
+ }
1122
+ getHistory(key) {
1123
+ if (key === void 0) {
1124
+ return this.history.getHistory();
1125
+ }
1126
+ return this.history.getHistory(key);
1127
+ }
1128
+ constructor(options) {
1129
+ const consumer = createStoreMessageConsumer(
1130
+ {
1131
+ getOrCreate: (key) => this.getOrCreate(key),
1132
+ getAllKeys: () => this.signals.keys()
1133
+ },
1134
+ {
1135
+ notify: (key, next, prev) => this.notifyHooks(key, next, prev)
1136
+ }
1137
+ );
1138
+ this.history = createStoreHistory({
1139
+ captureSnapshot: () => consumer.createSnapshot(),
1140
+ applySnapshot: (snapshot) => consumer.applySnapshot(snapshot),
1141
+ applyMessage: (message) => consumer.applyMessage(message),
1142
+ channel: options?.channel
1143
+ });
302
1144
  trackStore(this);
303
1145
  }
304
1146
  getOrCreate(key) {
@@ -309,138 +1151,55 @@ var LazyStore = class {
309
1151
  }
310
1152
  return sig;
311
1153
  }
1154
+ /** @inheritDoc */
312
1155
  get(key) {
313
1156
  return this.getOrCreate(key);
314
1157
  }
1158
+ /** @inheritDoc */
315
1159
  update(key, newState) {
316
- const sig = this.getOrCreate(key);
317
- const previousState = sig();
318
- sig.update((state) => ({ ...state, ...newState }));
319
- const nextState = sig();
320
- this.notifyHooks(key, nextState, previousState);
1160
+ this.history.publish(
1161
+ createUpdateMessage(key, cloneValue(newState))
1162
+ );
321
1163
  }
1164
+ /** @inheritDoc */
322
1165
  clear(key) {
323
- const sig = this.getOrCreate(key);
324
- const previousState = sig();
325
- sig.set(createDefaultState());
326
- const nextState = sig();
327
- this.notifyHooks(key, nextState, previousState);
1166
+ this.history.publish(createClearMessage(key));
328
1167
  }
1168
+ /** @inheritDoc */
329
1169
  clearAll() {
330
- for (const key of this.signals.keys()) {
331
- this.clear(key);
332
- }
1170
+ this.history.publish(createClearAllMessage());
333
1171
  }
1172
+ /** @inheritDoc */
334
1173
  startLoading(key) {
335
- const sig = this.getOrCreate(key);
336
- sig.update(
337
- (state) => ({
338
- ...state,
339
- status: void 0,
340
- isLoading: true,
341
- errors: void 0
342
- })
343
- );
1174
+ this.history.publish(createStartLoadingMessage(key));
344
1175
  }
1176
+ /** @inheritDoc */
345
1177
  stopLoading(key) {
346
- const sig = this.getOrCreate(key);
347
- sig.update(
348
- (state) => ({
349
- ...state,
350
- isLoading: false
351
- })
352
- );
1178
+ this.history.publish(createStopLoadingMessage(key));
353
1179
  }
1180
+ /** @inheritDoc */
354
1181
  updateKeyedOne(key, resourceKey, entity) {
355
- const sig = this.getOrCreate(key);
356
- const state = sig();
357
- const data = (0, import_core4.isKeyedResourceData)(state.data) ? state.data : (0, import_core4.createKeyedResourceData)();
358
- const nextErrors = { ...data.errors };
359
- delete nextErrors[resourceKey];
360
- const nextData = {
361
- ...data,
362
- entities: { ...data.entities, [resourceKey]: entity },
363
- isLoading: { ...data.isLoading, [resourceKey]: false },
364
- status: { ...data.status, [resourceKey]: "Success" },
365
- errors: nextErrors
366
- };
367
- this.update(key, {
368
- data: nextData,
369
- isLoading: (0, import_core4.isAnyKeyLoading)(nextData.isLoading),
370
- status: void 0,
371
- errors: void 0
372
- });
1182
+ this.history.publish(
1183
+ createUpdateKeyedOneMessage(
1184
+ key,
1185
+ resourceKey,
1186
+ cloneValue(entity)
1187
+ )
1188
+ );
373
1189
  }
1190
+ /** @inheritDoc */
374
1191
  clearKeyedOne(key, resourceKey) {
375
- const sig = this.getOrCreate(key);
376
- const state = sig();
377
- if (!(0, import_core4.isKeyedResourceData)(state.data)) {
378
- return;
379
- }
380
- const data = state.data;
381
- const previousState = state;
382
- const nextEntities = { ...data.entities };
383
- delete nextEntities[resourceKey];
384
- const nextIsLoading = { ...data.isLoading };
385
- delete nextIsLoading[resourceKey];
386
- const nextStatus = { ...data.status };
387
- delete nextStatus[resourceKey];
388
- const nextErrors = { ...data.errors };
389
- delete nextErrors[resourceKey];
390
- const nextData = {
391
- ...data,
392
- entities: nextEntities,
393
- isLoading: nextIsLoading,
394
- status: nextStatus,
395
- errors: nextErrors
396
- };
397
- sig.update(
398
- (prev) => ({
399
- ...prev,
400
- data: nextData,
401
- status: void 0,
402
- isLoading: (0, import_core4.isAnyKeyLoading)(nextIsLoading),
403
- errors: void 0
404
- })
1192
+ this.history.publish(
1193
+ createClearKeyedOneMessage(key, resourceKey)
405
1194
  );
406
- const updatedState = sig();
407
- this.notifyHooks(key, updatedState, previousState);
408
1195
  }
1196
+ /** @inheritDoc */
409
1197
  startKeyedLoading(key, resourceKey) {
410
- const sig = this.getOrCreate(key);
411
- const state = sig();
412
- if (!(0, import_core4.isKeyedResourceData)(state.data)) {
413
- this.startLoading(key);
414
- return;
415
- }
416
- const previousState = state;
417
- const data = state.data;
418
- const nextIsLoading = {
419
- ...data.isLoading,
420
- [resourceKey]: true
421
- };
422
- const nextStatus = { ...data.status };
423
- delete nextStatus[resourceKey];
424
- const nextErrors = { ...data.errors };
425
- delete nextErrors[resourceKey];
426
- const nextData = {
427
- ...data,
428
- isLoading: nextIsLoading,
429
- status: nextStatus,
430
- errors: nextErrors
431
- };
432
- sig.update(
433
- (previous) => ({
434
- ...previous,
435
- data: nextData,
436
- status: void 0,
437
- isLoading: (0, import_core4.isAnyKeyLoading)(nextIsLoading),
438
- errors: void 0
439
- })
1198
+ this.history.publish(
1199
+ createStartKeyedLoadingMessage(key, resourceKey)
440
1200
  );
441
- const updatedState = sig();
442
- this.notifyHooks(key, updatedState, previousState);
443
1201
  }
1202
+ /** @inheritDoc */
444
1203
  onUpdate(key, callback) {
445
1204
  if (!this.hooks.has(key)) {
446
1205
  this.hooks.set(key, []);
@@ -463,26 +1222,42 @@ var LazyStore = class {
463
1222
  if (!keyHooks) {
464
1223
  return;
465
1224
  }
466
- keyHooks.forEach(
467
- (hook) => hook(
468
- nextState,
469
- previousState
470
- )
471
- );
1225
+ const errors = [];
1226
+ keyHooks.forEach((hook) => {
1227
+ try {
1228
+ hook(
1229
+ nextState,
1230
+ previousState
1231
+ );
1232
+ } catch (error) {
1233
+ errors.push(error);
1234
+ }
1235
+ });
1236
+ if (errors.length > 0) {
1237
+ queueMicrotask(() => {
1238
+ if (errors.length === 1) {
1239
+ throw errors[0];
1240
+ }
1241
+ throw new AggregateError(
1242
+ errors,
1243
+ `${errors.length} onUpdate hooks threw for key "${String(key)}"`
1244
+ );
1245
+ });
1246
+ }
472
1247
  }
473
1248
  };
474
1249
 
475
1250
  // src/store-builder.ts
476
- var import_core6 = require("@angular/core");
1251
+ var import_core5 = require("@angular/core");
477
1252
 
478
1253
  // src/dynamic-store.ts
479
1254
  var DynamicStore = class extends BaseStore {
480
- constructor(config) {
1255
+ constructor(config, options) {
481
1256
  const identityEnum = Object.keys(config).reduce(
482
1257
  (acc, key) => ({ ...acc, [key]: key }),
483
1258
  {}
484
1259
  );
485
- super(identityEnum);
1260
+ super(identityEnum, options);
486
1261
  }
487
1262
  };
488
1263
 
@@ -503,12 +1278,12 @@ function mirrorKey(source, sourceKey, target, targetKeyOrOptions, options) {
503
1278
  }
504
1279
 
505
1280
  // src/collect-keyed.ts
506
- var import_core5 = require("@flurryx/core");
1281
+ var import_core4 = require("@flurryx/core");
507
1282
  function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
508
1283
  const resolvedTargetKey = typeof targetKeyOrOptions === "string" ? targetKeyOrOptions : sourceKey;
509
1284
  const resolvedOptions = typeof targetKeyOrOptions === "object" ? targetKeyOrOptions : options;
510
1285
  target.update(resolvedTargetKey, {
511
- data: (0, import_core5.createKeyedResourceData)()
1286
+ data: (0, import_core4.createKeyedResourceData)()
512
1287
  });
513
1288
  let previousId;
514
1289
  const cleanup = source.onUpdate(sourceKey, (state) => {
@@ -520,9 +1295,15 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
520
1295
  return;
521
1296
  }
522
1297
  if (resourceState.status === "Success" && currentId !== void 0) {
523
- const newEntities = { ...currentKeyed.entities, [currentId]: resourceState.data };
1298
+ const newEntities = {
1299
+ ...currentKeyed.entities,
1300
+ [currentId]: resourceState.data
1301
+ };
524
1302
  const newIsLoading = { ...currentKeyed.isLoading, [currentId]: false };
525
- const newStatus = { ...currentKeyed.status, [currentId]: resourceState.status };
1303
+ const newStatus = {
1304
+ ...currentKeyed.status,
1305
+ [currentId]: resourceState.status
1306
+ };
526
1307
  const newErrors = { ...currentKeyed.errors };
527
1308
  delete newErrors[currentId];
528
1309
  const updatedKeyed = {
@@ -533,14 +1314,20 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
533
1314
  };
534
1315
  target.update(resolvedTargetKey, {
535
1316
  data: updatedKeyed,
536
- isLoading: (0, import_core5.isAnyKeyLoading)(newIsLoading),
1317
+ isLoading: (0, import_core4.isAnyKeyLoading)(newIsLoading),
537
1318
  status: "Success"
538
1319
  });
539
1320
  previousId = currentId;
540
1321
  } else if (resourceState.status === "Error" && currentId !== void 0) {
541
1322
  const newIsLoading = { ...currentKeyed.isLoading, [currentId]: false };
542
- const newStatus = { ...currentKeyed.status, [currentId]: resourceState.status };
543
- const newErrors = { ...currentKeyed.errors, [currentId]: resourceState.errors };
1323
+ const newStatus = {
1324
+ ...currentKeyed.status,
1325
+ [currentId]: resourceState.status
1326
+ };
1327
+ const newErrors = {
1328
+ ...currentKeyed.errors,
1329
+ [currentId]: resourceState.errors
1330
+ };
544
1331
  const updatedKeyed = {
545
1332
  entities: { ...currentKeyed.entities },
546
1333
  isLoading: newIsLoading,
@@ -549,7 +1336,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
549
1336
  };
550
1337
  target.update(resolvedTargetKey, {
551
1338
  data: updatedKeyed,
552
- isLoading: (0, import_core5.isAnyKeyLoading)(newIsLoading)
1339
+ isLoading: (0, import_core4.isAnyKeyLoading)(newIsLoading)
553
1340
  });
554
1341
  previousId = currentId;
555
1342
  } else if (resourceState.data === void 0 && previousId !== void 0) {
@@ -565,7 +1352,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
565
1352
  };
566
1353
  target.update(resolvedTargetKey, {
567
1354
  data: updatedKeyed,
568
- isLoading: (0, import_core5.isAnyKeyLoading)(remainingLoading)
1355
+ isLoading: (0, import_core4.isAnyKeyLoading)(remainingLoading)
569
1356
  });
570
1357
  previousId = void 0;
571
1358
  } else if (resourceState.isLoading && currentId !== void 0) {
@@ -597,7 +1384,7 @@ function resource() {
597
1384
  // src/store-builder.ts
598
1385
  function wireMirrors(store, mirrors) {
599
1386
  for (const def of mirrors) {
600
- const sourceStore = (0, import_core6.inject)(def.sourceToken);
1387
+ const sourceStore = (0, import_core5.inject)(def.sourceToken);
601
1388
  mirrorKey(
602
1389
  sourceStore,
603
1390
  def.sourceKey,
@@ -605,11 +1392,10 @@ function wireMirrors(store, mirrors) {
605
1392
  def.targetKey
606
1393
  );
607
1394
  }
608
- return store;
609
1395
  }
610
1396
  function wireMirrorKeyed(store, defs) {
611
1397
  for (const def of defs) {
612
- const sourceStore = (0, import_core6.inject)(def.sourceToken);
1398
+ const sourceStore = (0, import_core5.inject)(def.sourceToken);
613
1399
  collectKeyed(
614
1400
  sourceStore,
615
1401
  def.sourceKey,
@@ -620,7 +1406,6 @@ function wireMirrorKeyed(store, defs) {
620
1406
  }
621
1407
  );
622
1408
  }
623
- return store;
624
1409
  }
625
1410
  var MIRROR_SELF_SAME_KEY_ERROR = "mirrorSelf source and target keys must be different";
626
1411
  function wireSelfMirrors(store, defs) {
@@ -635,7 +1420,6 @@ function wireSelfMirrors(store, defs) {
635
1420
  def.targetKey
636
1421
  );
637
1422
  }
638
- return store;
639
1423
  }
640
1424
  function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors = []) {
641
1425
  return {
@@ -692,11 +1476,11 @@ function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors =
692
1476
  selfMirrors
693
1477
  );
694
1478
  },
695
- build() {
696
- return new import_core6.InjectionToken("FlurryxStore", {
1479
+ build(options) {
1480
+ return new import_core5.InjectionToken("FlurryxStore", {
697
1481
  providedIn: "root",
698
1482
  factory: () => {
699
- const store = new DynamicStore(accum);
1483
+ const store = new DynamicStore(accum, options);
700
1484
  wireMirrors(store, mirrors);
701
1485
  wireMirrorKeyed(store, mirrorKeyedDefs);
702
1486
  wireSelfMirrors(store, selfMirrors);
@@ -767,11 +1551,11 @@ function createConstrainedBuilder(_enumObj, accum, mirrors = [], mirrorKeyedDefs
767
1551
  selfMirrors
768
1552
  );
769
1553
  },
770
- build() {
771
- return new import_core6.InjectionToken("FlurryxStore", {
1554
+ build(options) {
1555
+ return new import_core5.InjectionToken("FlurryxStore", {
772
1556
  providedIn: "root",
773
1557
  factory: () => {
774
- const store = new DynamicStore(accum);
1558
+ const store = new DynamicStore(accum, options);
775
1559
  wireMirrors(store, mirrors);
776
1560
  wireMirrorKeyed(store, mirrorKeyedDefs);
777
1561
  wireSelfMirrors(store, selfMirrors);
@@ -818,11 +1602,11 @@ function createInterfaceBuilder(mirrors = [], mirrorKeyedDefs = [], selfMirrors
818
1602
  selfMirrors
819
1603
  );
820
1604
  },
821
- build() {
822
- return new import_core6.InjectionToken("FlurryxStore", {
1605
+ build(options) {
1606
+ return new import_core5.InjectionToken("FlurryxStore", {
823
1607
  providedIn: "root",
824
1608
  factory: () => {
825
- const store = new LazyStore();
1609
+ const store = new LazyStore(options);
826
1610
  wireMirrors(store, mirrors);
827
1611
  wireMirrorKeyed(store, mirrorKeyedDefs);
828
1612
  wireSelfMirrors(store, selfMirrors);
@@ -848,7 +1632,14 @@ function createStoreFor(enumObj) {
848
1632
  LazyStore,
849
1633
  Store,
850
1634
  clearAllStores,
1635
+ cloneValue,
851
1636
  collectKeyed,
1637
+ createCompositeStoreMessageChannel,
1638
+ createInMemoryStoreMessageChannel,
1639
+ createLocalStorageStoreMessageChannel,
1640
+ createSessionStorageStoreMessageChannel,
1641
+ createSnapshotRestorePatch,
1642
+ createStorageStoreMessageChannel,
852
1643
  mirrorKey
853
1644
  });
854
1645
  //# sourceMappingURL=index.cjs.map