@flurryx/store 0.8.4 → 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,14 +24,598 @@ __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");
41
+
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;
49
+ }
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);
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;
121
+ }
122
+
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" };
132
+ }
133
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
134
+ return value;
135
+ }
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;
213
+ }
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
+ });
357
+ return;
358
+ }
359
+ const nextMessages = state.messages.map(
360
+ (c, i) => i === existingIndex ? record : c
361
+ );
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
452
+ }
453
+ ];
454
+ currentIndex = nextIndex;
455
+ }
456
+ function truncateFutureHistory() {
457
+ if (currentIndex === history.length - 1) {
458
+ return;
459
+ }
460
+ history = history.slice(0, currentIndex + 1);
461
+ }
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);
550
+ });
551
+ }
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;
567
+ }
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
+ }
577
+ });
578
+ return acknowledgedCount;
579
+ }
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
+ }
35
619
 
36
620
  // src/store-registry.ts
37
621
  var trackedStores = /* @__PURE__ */ new Set();
@@ -44,16 +628,305 @@ function clearAllStores() {
44
628
  }
45
629
  }
46
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;
664
+ }
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(
673
+ (state) => ({
674
+ ...state,
675
+ status: void 0,
676
+ isLoading: true,
677
+ errors: void 0
678
+ })
679
+ );
680
+ return true;
681
+ }
682
+ function applyStopLoading(key) {
683
+ const sig = signals.getOrCreate(key);
684
+ sig.update(
685
+ (state) => ({
686
+ ...state,
687
+ isLoading: false
688
+ })
689
+ );
690
+ return true;
691
+ }
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)();
696
+ const nextErrors = { ...data.errors };
697
+ delete nextErrors[resourceKey];
698
+ const nextData = {
699
+ ...data,
700
+ entities: { ...data.entities, [resourceKey]: entity },
701
+ isLoading: { ...data.isLoading, [resourceKey]: false },
702
+ status: { ...data.status, [resourceKey]: "Success" },
703
+ errors: nextErrors
704
+ };
705
+ return applyUpdate(key, {
706
+ data: nextData,
707
+ isLoading: (0, import_core.isAnyKeyLoading)(nextData.isLoading),
708
+ status: void 0,
709
+ errors: void 0
710
+ });
711
+ }
712
+ function applyClearKeyedOne(key, resourceKey) {
713
+ const sig = signals.getOrCreate(key);
714
+ const previousState = sig();
715
+ const state = previousState;
716
+ if (!(0, import_core.isKeyedResourceData)(state.data)) {
717
+ return true;
718
+ }
719
+ const data = state.data;
720
+ const nextEntities = { ...data.entities };
721
+ delete nextEntities[resourceKey];
722
+ const nextIsLoading = { ...data.isLoading };
723
+ delete nextIsLoading[resourceKey];
724
+ const nextStatus = { ...data.status };
725
+ delete nextStatus[resourceKey];
726
+ const nextErrors = { ...data.errors };
727
+ delete nextErrors[resourceKey];
728
+ const nextData = {
729
+ ...data,
730
+ entities: nextEntities,
731
+ isLoading: nextIsLoading,
732
+ status: nextStatus,
733
+ errors: nextErrors
734
+ };
735
+ sig.update(
736
+ (prev) => ({
737
+ ...prev,
738
+ data: nextData,
739
+ status: void 0,
740
+ isLoading: (0, import_core.isAnyKeyLoading)(nextIsLoading),
741
+ errors: void 0
742
+ })
743
+ );
744
+ const updatedState = sig();
745
+ notifier.notify(key, updatedState, previousState);
746
+ return true;
747
+ }
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);
753
+ }
754
+ const previousState = state;
755
+ const data = state.data;
756
+ const nextIsLoading = {
757
+ ...data.isLoading,
758
+ [resourceKey]: true
759
+ };
760
+ const nextStatus = { ...data.status };
761
+ delete nextStatus[resourceKey];
762
+ const nextErrors = { ...data.errors };
763
+ delete nextErrors[resourceKey];
764
+ const nextData = {
765
+ ...data,
766
+ isLoading: nextIsLoading,
767
+ status: nextStatus,
768
+ errors: nextErrors
769
+ };
770
+ sig.update(
771
+ (previous) => ({
772
+ ...previous,
773
+ data: nextData,
774
+ status: void 0,
775
+ isLoading: (0, import_core.isAnyKeyLoading)(nextIsLoading),
776
+ errors: void 0
777
+ })
778
+ );
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
+
47
870
  // src/base-store.ts
48
871
  var updateHooksMap = /* @__PURE__ */ new WeakMap();
49
872
  var BaseStore = class {
50
- constructor(storeEnum) {
873
+ constructor(storeEnum, options) {
51
874
  this.storeEnum = storeEnum;
875
+ this.storeKeys = Object.keys(storeEnum);
52
876
  this.initializeState();
53
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
+ });
54
893
  trackStore(this);
55
894
  }
56
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
+ }
57
930
  /**
58
931
  * Returns a **read-only** `Signal` for the given store slot.
59
932
  *
@@ -98,23 +971,13 @@ var BaseStore = class {
98
971
  * @param newState - Partial state to merge (e.g. `{ data: newData, status: 'Success' }`).
99
972
  */
100
973
  update(key, newState) {
101
- const currentState = this.signalsState.get(key.toString());
102
- if (!currentState) {
103
- return;
104
- }
105
- const previousState = currentState();
106
- currentState.update((state) => ({
107
- ...state,
108
- ...newState
109
- }));
110
- const updatedState = currentState();
111
- this.notifyUpdateHooks(key, updatedState, previousState);
974
+ this.history.publish(
975
+ createUpdateMessage(key, cloneValue(newState))
976
+ );
112
977
  }
113
978
  /** Resets every slot in this store to its initial idle state. */
114
979
  clearAll() {
115
- Object.keys(this.storeEnum).forEach((key) => {
116
- this.clear(key);
117
- });
980
+ this.history.publish(createClearAllMessage());
118
981
  }
119
982
  /**
120
983
  * Resets a single slot to `{ data: undefined, isLoading: false, status: undefined, errors: undefined }`.
@@ -122,20 +985,7 @@ var BaseStore = class {
122
985
  * @param key - The slot to clear.
123
986
  */
124
987
  clear(key) {
125
- const currentState = this.signalsState.get(key.toString());
126
- if (!currentState) {
127
- return;
128
- }
129
- const previousState = currentState();
130
- const _typedKey = key;
131
- currentState.set({
132
- data: void 0,
133
- isLoading: false,
134
- status: void 0,
135
- errors: void 0
136
- });
137
- const nextState = currentState();
138
- this.notifyUpdateHooks(key, nextState, previousState);
988
+ this.history.publish(createClearMessage(key));
139
989
  }
140
990
  /**
141
991
  * Marks a slot as loading: sets `isLoading: true` and clears `status` and `errors`.
@@ -143,19 +993,7 @@ var BaseStore = class {
143
993
  * @param key - The slot to mark as loading.
144
994
  */
145
995
  startLoading(key) {
146
- const currentState = this.signalsState.get(key.toString());
147
- if (!currentState) {
148
- return;
149
- }
150
- const _typedKey = key;
151
- currentState.update(
152
- (state) => ({
153
- ...state,
154
- status: void 0,
155
- isLoading: true,
156
- errors: void 0
157
- })
158
- );
996
+ this.history.publish(createStartLoadingMessage(key));
159
997
  }
160
998
  /**
161
999
  * Marks a slot as no longer loading: sets `isLoading: false`.
@@ -164,17 +1002,7 @@ var BaseStore = class {
164
1002
  * @param key - The slot to stop loading.
165
1003
  */
166
1004
  stopLoading(key) {
167
- const currentState = this.signalsState.get(key.toString());
168
- if (!currentState) {
169
- return;
170
- }
171
- const _typedKey = key;
172
- currentState.update(
173
- (state) => ({
174
- ...state,
175
- isLoading: false
176
- })
177
- );
1005
+ this.history.publish(createStopLoadingMessage(key));
178
1006
  }
179
1007
  /**
180
1008
  * Merges a single entity into a {@link KeyedResourceData} slot.
@@ -186,36 +1014,13 @@ var BaseStore = class {
186
1014
  * @param entity - The entity value to store.
187
1015
  */
188
1016
  updateKeyedOne(key, resourceKey, entity) {
189
- const currentState = this.signalsState.get(key.toString());
190
- if (!currentState) {
191
- return;
192
- }
193
- const state = currentState();
194
- const data = (0, import_core2.isKeyedResourceData)(state.data) ? state.data : (0, import_core2.createKeyedResourceData)();
195
- const nextErrors = { ...data.errors };
196
- delete nextErrors[resourceKey];
197
- const nextData = {
198
- ...data,
199
- entities: {
200
- ...data.entities,
201
- [resourceKey]: entity
202
- },
203
- isLoading: {
204
- ...data.isLoading,
205
- [resourceKey]: false
206
- },
207
- status: {
208
- ...data.status,
209
- [resourceKey]: "Success"
210
- },
211
- errors: nextErrors
212
- };
213
- this.update(key, {
214
- data: nextData,
215
- isLoading: (0, import_core2.isAnyKeyLoading)(nextData.isLoading),
216
- status: void 0,
217
- errors: void 0
218
- });
1017
+ this.history.publish(
1018
+ createUpdateKeyedOneMessage(
1019
+ key,
1020
+ resourceKey,
1021
+ cloneValue(entity)
1022
+ )
1023
+ );
219
1024
  }
220
1025
  /**
221
1026
  * Removes a single entity from a {@link KeyedResourceData} slot,
@@ -226,43 +1031,9 @@ var BaseStore = class {
226
1031
  * @param resourceKey - The entity identifier to remove.
227
1032
  */
228
1033
  clearKeyedOne(key, resourceKey) {
229
- const currentState = this.signalsState.get(key.toString());
230
- if (!currentState) {
231
- return;
232
- }
233
- const previousState = currentState();
234
- const state = previousState;
235
- if (!(0, import_core2.isKeyedResourceData)(state.data)) {
236
- return;
237
- }
238
- const data = state.data;
239
- const nextEntities = { ...data.entities };
240
- delete nextEntities[resourceKey];
241
- const nextIsLoading = { ...data.isLoading };
242
- delete nextIsLoading[resourceKey];
243
- const nextStatus = { ...data.status };
244
- delete nextStatus[resourceKey];
245
- const nextErrors = { ...data.errors };
246
- delete nextErrors[resourceKey];
247
- const nextData = {
248
- ...data,
249
- entities: nextEntities,
250
- isLoading: nextIsLoading,
251
- status: nextStatus,
252
- errors: nextErrors
253
- };
254
- const _typedKey = key;
255
- currentState.update(
256
- (prev) => ({
257
- ...prev,
258
- data: nextData,
259
- status: void 0,
260
- isLoading: (0, import_core2.isAnyKeyLoading)(nextIsLoading),
261
- errors: void 0
262
- })
1034
+ this.history.publish(
1035
+ createClearKeyedOneMessage(key, resourceKey)
263
1036
  );
264
- const updatedState = currentState();
265
- this.notifyUpdateHooks(key, updatedState, previousState);
266
1037
  }
267
1038
  /**
268
1039
  * Marks a single entity within a keyed slot as loading.
@@ -273,47 +1044,9 @@ var BaseStore = class {
273
1044
  * @param resourceKey - The entity identifier to mark as loading.
274
1045
  */
275
1046
  startKeyedLoading(key, resourceKey) {
276
- const currentState = this.signalsState.get(key.toString());
277
- if (!currentState) {
278
- return;
279
- }
280
- const _typedKey = key;
281
- const state = currentState();
282
- if (!(0, import_core2.isKeyedResourceData)(state.data)) {
283
- this.startLoading(key);
284
- return;
285
- }
286
- const previousState = state;
287
- const data = state.data;
288
- const nextIsLoading = {
289
- ...data.isLoading,
290
- [resourceKey]: true
291
- };
292
- const nextStatus = {
293
- ...data.status
294
- };
295
- delete nextStatus[resourceKey];
296
- const nextErrors = {
297
- ...data.errors
298
- };
299
- delete nextErrors[resourceKey];
300
- const nextData = {
301
- ...data,
302
- isLoading: nextIsLoading,
303
- status: nextStatus,
304
- errors: nextErrors
305
- };
306
- currentState.update(
307
- (previous) => ({
308
- ...previous,
309
- data: nextData,
310
- status: void 0,
311
- isLoading: (0, import_core2.isAnyKeyLoading)(nextIsLoading),
312
- errors: void 0
313
- })
1047
+ this.history.publish(
1048
+ createStartKeyedLoadingMessage(key, resourceKey)
314
1049
  );
315
- const updatedState = currentState();
316
- this.notifyUpdateHooks(key, updatedState, previousState);
317
1050
  }
318
1051
  notifyUpdateHooks(key, nextState, previousState) {
319
1052
  const hooks = updateHooksMap.get(this);
@@ -321,25 +1054,34 @@ var BaseStore = class {
321
1054
  if (!keyHooks) {
322
1055
  return;
323
1056
  }
324
- keyHooks.forEach(
325
- (hook) => hook(
326
- nextState,
327
- previousState
328
- )
329
- );
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
+ }
330
1079
  }
331
1080
  initializeState() {
332
- Object.keys(this.storeEnum).forEach((key) => {
333
- const _typedKey = key;
334
- const initialState = {
335
- data: void 0,
336
- isLoading: false,
337
- status: void 0,
338
- errors: void 0
339
- };
1081
+ this.storeKeys.forEach((key) => {
340
1082
  this.signalsState.set(
341
- _typedKey,
342
- (0, import_core.signal)(initialState)
1083
+ key,
1084
+ (0, import_core2.signal)(createDefaultState())
343
1085
  );
344
1086
  });
345
1087
  }
@@ -347,19 +1089,58 @@ var BaseStore = class {
347
1089
 
348
1090
  // src/lazy-store.ts
349
1091
  var import_core3 = require("@angular/core");
350
- var import_core4 = require("@flurryx/core");
351
- function createDefaultState() {
352
- return {
353
- data: void 0,
354
- isLoading: false,
355
- status: void 0,
356
- errors: void 0
357
- };
358
- }
359
1092
  var LazyStore = class {
360
1093
  signals = /* @__PURE__ */ new Map();
361
1094
  hooks = /* @__PURE__ */ new Map();
362
- 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
+ });
363
1144
  trackStore(this);
364
1145
  }
365
1146
  getOrCreate(key) {
@@ -370,138 +1151,55 @@ var LazyStore = class {
370
1151
  }
371
1152
  return sig;
372
1153
  }
1154
+ /** @inheritDoc */
373
1155
  get(key) {
374
1156
  return this.getOrCreate(key);
375
1157
  }
1158
+ /** @inheritDoc */
376
1159
  update(key, newState) {
377
- const sig = this.getOrCreate(key);
378
- const previousState = sig();
379
- sig.update((state) => ({ ...state, ...newState }));
380
- const nextState = sig();
381
- this.notifyHooks(key, nextState, previousState);
1160
+ this.history.publish(
1161
+ createUpdateMessage(key, cloneValue(newState))
1162
+ );
382
1163
  }
1164
+ /** @inheritDoc */
383
1165
  clear(key) {
384
- const sig = this.getOrCreate(key);
385
- const previousState = sig();
386
- sig.set(createDefaultState());
387
- const nextState = sig();
388
- this.notifyHooks(key, nextState, previousState);
1166
+ this.history.publish(createClearMessage(key));
389
1167
  }
1168
+ /** @inheritDoc */
390
1169
  clearAll() {
391
- for (const key of this.signals.keys()) {
392
- this.clear(key);
393
- }
1170
+ this.history.publish(createClearAllMessage());
394
1171
  }
1172
+ /** @inheritDoc */
395
1173
  startLoading(key) {
396
- const sig = this.getOrCreate(key);
397
- sig.update(
398
- (state) => ({
399
- ...state,
400
- status: void 0,
401
- isLoading: true,
402
- errors: void 0
403
- })
404
- );
1174
+ this.history.publish(createStartLoadingMessage(key));
405
1175
  }
1176
+ /** @inheritDoc */
406
1177
  stopLoading(key) {
407
- const sig = this.getOrCreate(key);
408
- sig.update(
409
- (state) => ({
410
- ...state,
411
- isLoading: false
412
- })
413
- );
1178
+ this.history.publish(createStopLoadingMessage(key));
414
1179
  }
1180
+ /** @inheritDoc */
415
1181
  updateKeyedOne(key, resourceKey, entity) {
416
- const sig = this.getOrCreate(key);
417
- const state = sig();
418
- const data = (0, import_core4.isKeyedResourceData)(state.data) ? state.data : (0, import_core4.createKeyedResourceData)();
419
- const nextErrors = { ...data.errors };
420
- delete nextErrors[resourceKey];
421
- const nextData = {
422
- ...data,
423
- entities: { ...data.entities, [resourceKey]: entity },
424
- isLoading: { ...data.isLoading, [resourceKey]: false },
425
- status: { ...data.status, [resourceKey]: "Success" },
426
- errors: nextErrors
427
- };
428
- this.update(key, {
429
- data: nextData,
430
- isLoading: (0, import_core4.isAnyKeyLoading)(nextData.isLoading),
431
- status: void 0,
432
- errors: void 0
433
- });
1182
+ this.history.publish(
1183
+ createUpdateKeyedOneMessage(
1184
+ key,
1185
+ resourceKey,
1186
+ cloneValue(entity)
1187
+ )
1188
+ );
434
1189
  }
1190
+ /** @inheritDoc */
435
1191
  clearKeyedOne(key, resourceKey) {
436
- const sig = this.getOrCreate(key);
437
- const state = sig();
438
- if (!(0, import_core4.isKeyedResourceData)(state.data)) {
439
- return;
440
- }
441
- const data = state.data;
442
- const previousState = state;
443
- const nextEntities = { ...data.entities };
444
- delete nextEntities[resourceKey];
445
- const nextIsLoading = { ...data.isLoading };
446
- delete nextIsLoading[resourceKey];
447
- const nextStatus = { ...data.status };
448
- delete nextStatus[resourceKey];
449
- const nextErrors = { ...data.errors };
450
- delete nextErrors[resourceKey];
451
- const nextData = {
452
- ...data,
453
- entities: nextEntities,
454
- isLoading: nextIsLoading,
455
- status: nextStatus,
456
- errors: nextErrors
457
- };
458
- sig.update(
459
- (prev) => ({
460
- ...prev,
461
- data: nextData,
462
- status: void 0,
463
- isLoading: (0, import_core4.isAnyKeyLoading)(nextIsLoading),
464
- errors: void 0
465
- })
1192
+ this.history.publish(
1193
+ createClearKeyedOneMessage(key, resourceKey)
466
1194
  );
467
- const updatedState = sig();
468
- this.notifyHooks(key, updatedState, previousState);
469
1195
  }
1196
+ /** @inheritDoc */
470
1197
  startKeyedLoading(key, resourceKey) {
471
- const sig = this.getOrCreate(key);
472
- const state = sig();
473
- if (!(0, import_core4.isKeyedResourceData)(state.data)) {
474
- this.startLoading(key);
475
- return;
476
- }
477
- const previousState = state;
478
- const data = state.data;
479
- const nextIsLoading = {
480
- ...data.isLoading,
481
- [resourceKey]: true
482
- };
483
- const nextStatus = { ...data.status };
484
- delete nextStatus[resourceKey];
485
- const nextErrors = { ...data.errors };
486
- delete nextErrors[resourceKey];
487
- const nextData = {
488
- ...data,
489
- isLoading: nextIsLoading,
490
- status: nextStatus,
491
- errors: nextErrors
492
- };
493
- sig.update(
494
- (previous) => ({
495
- ...previous,
496
- data: nextData,
497
- status: void 0,
498
- isLoading: (0, import_core4.isAnyKeyLoading)(nextIsLoading),
499
- errors: void 0
500
- })
1198
+ this.history.publish(
1199
+ createStartKeyedLoadingMessage(key, resourceKey)
501
1200
  );
502
- const updatedState = sig();
503
- this.notifyHooks(key, updatedState, previousState);
504
1201
  }
1202
+ /** @inheritDoc */
505
1203
  onUpdate(key, callback) {
506
1204
  if (!this.hooks.has(key)) {
507
1205
  this.hooks.set(key, []);
@@ -524,26 +1222,42 @@ var LazyStore = class {
524
1222
  if (!keyHooks) {
525
1223
  return;
526
1224
  }
527
- keyHooks.forEach(
528
- (hook) => hook(
529
- nextState,
530
- previousState
531
- )
532
- );
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
+ }
533
1247
  }
534
1248
  };
535
1249
 
536
1250
  // src/store-builder.ts
537
- var import_core6 = require("@angular/core");
1251
+ var import_core5 = require("@angular/core");
538
1252
 
539
1253
  // src/dynamic-store.ts
540
1254
  var DynamicStore = class extends BaseStore {
541
- constructor(config) {
1255
+ constructor(config, options) {
542
1256
  const identityEnum = Object.keys(config).reduce(
543
1257
  (acc, key) => ({ ...acc, [key]: key }),
544
1258
  {}
545
1259
  );
546
- super(identityEnum);
1260
+ super(identityEnum, options);
547
1261
  }
548
1262
  };
549
1263
 
@@ -564,12 +1278,12 @@ function mirrorKey(source, sourceKey, target, targetKeyOrOptions, options) {
564
1278
  }
565
1279
 
566
1280
  // src/collect-keyed.ts
567
- var import_core5 = require("@flurryx/core");
1281
+ var import_core4 = require("@flurryx/core");
568
1282
  function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
569
1283
  const resolvedTargetKey = typeof targetKeyOrOptions === "string" ? targetKeyOrOptions : sourceKey;
570
1284
  const resolvedOptions = typeof targetKeyOrOptions === "object" ? targetKeyOrOptions : options;
571
1285
  target.update(resolvedTargetKey, {
572
- data: (0, import_core5.createKeyedResourceData)()
1286
+ data: (0, import_core4.createKeyedResourceData)()
573
1287
  });
574
1288
  let previousId;
575
1289
  const cleanup = source.onUpdate(sourceKey, (state) => {
@@ -600,7 +1314,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
600
1314
  };
601
1315
  target.update(resolvedTargetKey, {
602
1316
  data: updatedKeyed,
603
- isLoading: (0, import_core5.isAnyKeyLoading)(newIsLoading),
1317
+ isLoading: (0, import_core4.isAnyKeyLoading)(newIsLoading),
604
1318
  status: "Success"
605
1319
  });
606
1320
  previousId = currentId;
@@ -622,7 +1336,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
622
1336
  };
623
1337
  target.update(resolvedTargetKey, {
624
1338
  data: updatedKeyed,
625
- isLoading: (0, import_core5.isAnyKeyLoading)(newIsLoading)
1339
+ isLoading: (0, import_core4.isAnyKeyLoading)(newIsLoading)
626
1340
  });
627
1341
  previousId = currentId;
628
1342
  } else if (resourceState.data === void 0 && previousId !== void 0) {
@@ -638,7 +1352,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
638
1352
  };
639
1353
  target.update(resolvedTargetKey, {
640
1354
  data: updatedKeyed,
641
- isLoading: (0, import_core5.isAnyKeyLoading)(remainingLoading)
1355
+ isLoading: (0, import_core4.isAnyKeyLoading)(remainingLoading)
642
1356
  });
643
1357
  previousId = void 0;
644
1358
  } else if (resourceState.isLoading && currentId !== void 0) {
@@ -670,7 +1384,7 @@ function resource() {
670
1384
  // src/store-builder.ts
671
1385
  function wireMirrors(store, mirrors) {
672
1386
  for (const def of mirrors) {
673
- const sourceStore = (0, import_core6.inject)(def.sourceToken);
1387
+ const sourceStore = (0, import_core5.inject)(def.sourceToken);
674
1388
  mirrorKey(
675
1389
  sourceStore,
676
1390
  def.sourceKey,
@@ -678,11 +1392,10 @@ function wireMirrors(store, mirrors) {
678
1392
  def.targetKey
679
1393
  );
680
1394
  }
681
- return store;
682
1395
  }
683
1396
  function wireMirrorKeyed(store, defs) {
684
1397
  for (const def of defs) {
685
- const sourceStore = (0, import_core6.inject)(def.sourceToken);
1398
+ const sourceStore = (0, import_core5.inject)(def.sourceToken);
686
1399
  collectKeyed(
687
1400
  sourceStore,
688
1401
  def.sourceKey,
@@ -693,7 +1406,6 @@ function wireMirrorKeyed(store, defs) {
693
1406
  }
694
1407
  );
695
1408
  }
696
- return store;
697
1409
  }
698
1410
  var MIRROR_SELF_SAME_KEY_ERROR = "mirrorSelf source and target keys must be different";
699
1411
  function wireSelfMirrors(store, defs) {
@@ -708,7 +1420,6 @@ function wireSelfMirrors(store, defs) {
708
1420
  def.targetKey
709
1421
  );
710
1422
  }
711
- return store;
712
1423
  }
713
1424
  function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors = []) {
714
1425
  return {
@@ -765,11 +1476,11 @@ function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors =
765
1476
  selfMirrors
766
1477
  );
767
1478
  },
768
- build() {
769
- return new import_core6.InjectionToken("FlurryxStore", {
1479
+ build(options) {
1480
+ return new import_core5.InjectionToken("FlurryxStore", {
770
1481
  providedIn: "root",
771
1482
  factory: () => {
772
- const store = new DynamicStore(accum);
1483
+ const store = new DynamicStore(accum, options);
773
1484
  wireMirrors(store, mirrors);
774
1485
  wireMirrorKeyed(store, mirrorKeyedDefs);
775
1486
  wireSelfMirrors(store, selfMirrors);
@@ -840,11 +1551,11 @@ function createConstrainedBuilder(_enumObj, accum, mirrors = [], mirrorKeyedDefs
840
1551
  selfMirrors
841
1552
  );
842
1553
  },
843
- build() {
844
- return new import_core6.InjectionToken("FlurryxStore", {
1554
+ build(options) {
1555
+ return new import_core5.InjectionToken("FlurryxStore", {
845
1556
  providedIn: "root",
846
1557
  factory: () => {
847
- const store = new DynamicStore(accum);
1558
+ const store = new DynamicStore(accum, options);
848
1559
  wireMirrors(store, mirrors);
849
1560
  wireMirrorKeyed(store, mirrorKeyedDefs);
850
1561
  wireSelfMirrors(store, selfMirrors);
@@ -891,11 +1602,11 @@ function createInterfaceBuilder(mirrors = [], mirrorKeyedDefs = [], selfMirrors
891
1602
  selfMirrors
892
1603
  );
893
1604
  },
894
- build() {
895
- return new import_core6.InjectionToken("FlurryxStore", {
1605
+ build(options) {
1606
+ return new import_core5.InjectionToken("FlurryxStore", {
896
1607
  providedIn: "root",
897
1608
  factory: () => {
898
- const store = new LazyStore();
1609
+ const store = new LazyStore(options);
899
1610
  wireMirrors(store, mirrors);
900
1611
  wireMirrorKeyed(store, mirrorKeyedDefs);
901
1612
  wireSelfMirrors(store, selfMirrors);
@@ -921,7 +1632,14 @@ function createStoreFor(enumObj) {
921
1632
  LazyStore,
922
1633
  Store,
923
1634
  clearAllStores,
1635
+ cloneValue,
924
1636
  collectKeyed,
1637
+ createCompositeStoreMessageChannel,
1638
+ createInMemoryStoreMessageChannel,
1639
+ createLocalStorageStoreMessageChannel,
1640
+ createSessionStorageStoreMessageChannel,
1641
+ createSnapshotRestorePatch,
1642
+ createStorageStoreMessageChannel,
925
1643
  mirrorKey
926
1644
  });
927
1645
  //# sourceMappingURL=index.cjs.map