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