@flurryx/store 0.8.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,97 +1,641 @@
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";
8
3
 
9
- // src/store-registry.ts
10
- var trackedStores = /* @__PURE__ */ new Set();
11
- function trackStore(store) {
12
- trackedStores.add(store);
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;
13
11
  }
14
- function clearAllStores() {
15
- for (const store of [...trackedStores]) {
16
- store.clearAll();
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;
17
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;
18
83
  }
19
84
 
20
- // src/base-store.ts
21
- var updateHooksMap = /* @__PURE__ */ new WeakMap();
22
- var BaseStore = class {
23
- constructor(storeEnum) {
24
- this.storeEnum = storeEnum;
25
- this.initializeState();
26
- updateHooksMap.set(this, /* @__PURE__ */ new Map());
27
- trackStore(this);
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" };
28
94
  }
29
- signalsState = /* @__PURE__ */ new Map();
30
- get(key) {
31
- return this.signalsState.get(key.toString());
95
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
96
+ return value;
32
97
  }
33
- onUpdate(key, callback) {
34
- const hooks = updateHooksMap.get(this);
35
- if (!hooks.has(key)) {
36
- hooks.set(key, []);
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;
37
175
  }
38
- hooks.get(key).push(
39
- callback
40
- );
41
- return () => {
42
- const hooksMap = hooks.get(key);
43
- if (!hooksMap) {
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
+ });
44
319
  return;
45
320
  }
46
- const index = hooksMap.indexOf(
47
- callback
321
+ const nextMessages = state.messages.map(
322
+ (c, i) => i === existingIndex ? record : c
48
323
  );
49
- if (index > -1) {
50
- hooksMap.splice(index, 1);
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
51
414
  }
52
- };
415
+ ];
416
+ currentIndex = nextIndex;
53
417
  }
54
- update(key, newState) {
55
- const currentState = this.signalsState.get(key.toString());
56
- if (!currentState) {
418
+ function truncateFutureHistory() {
419
+ if (currentIndex === history.length - 1) {
57
420
  return;
58
421
  }
59
- const previousState = currentState();
60
- currentState.update((state) => ({
61
- ...state,
62
- ...newState
63
- }));
64
- const updatedState = currentState();
65
- this.notifyUpdateHooks(key, updatedState, previousState);
422
+ history = history.slice(0, currentIndex + 1);
66
423
  }
67
- clearAll() {
68
- Object.keys(this.storeEnum).forEach((key) => {
69
- this.clear(key);
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);
70
512
  });
71
513
  }
72
- clear(key) {
73
- const currentState = this.signalsState.get(key.toString());
74
- if (!currentState) {
75
- return;
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;
76
529
  }
77
- const previousState = currentState();
78
- const _typedKey = key;
79
- currentState.set({
80
- data: void 0,
81
- isLoading: false,
82
- status: void 0,
83
- errors: void 0
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
+ }
84
539
  });
85
- const nextState = currentState();
86
- this.notifyUpdateHooks(key, nextState, previousState);
540
+ return acknowledgedCount;
87
541
  }
88
- startLoading(key) {
89
- const currentState = this.signalsState.get(key.toString());
90
- if (!currentState) {
91
- return;
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
+ }
581
+
582
+ // src/store-registry.ts
583
+ var trackedStores = /* @__PURE__ */ new Set();
584
+ function trackStore(store) {
585
+ trackedStores.add(store);
586
+ }
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);
92
615
  }
93
- const _typedKey = key;
94
- currentState.update(
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(
95
639
  (state) => ({
96
640
  ...state,
97
641
  status: void 0,
@@ -99,61 +643,44 @@ var BaseStore = class {
99
643
  errors: void 0
100
644
  })
101
645
  );
646
+ return true;
102
647
  }
103
- stopLoading(key) {
104
- const currentState = this.signalsState.get(key.toString());
105
- if (!currentState) {
106
- return;
107
- }
108
- const _typedKey = key;
109
- currentState.update(
648
+ function applyStopLoading(key) {
649
+ const sig = signals.getOrCreate(key);
650
+ sig.update(
110
651
  (state) => ({
111
652
  ...state,
112
653
  isLoading: false
113
654
  })
114
655
  );
656
+ return true;
115
657
  }
116
- updateKeyedOne(key, resourceKey, entity) {
117
- const currentState = this.signalsState.get(key.toString());
118
- if (!currentState) {
119
- return;
120
- }
121
- const state = currentState();
658
+ function applyUpdateKeyedOne(key, resourceKey, entity) {
659
+ const sig = signals.getOrCreate(key);
660
+ const state = sig();
122
661
  const data = isKeyedResourceData(state.data) ? state.data : createKeyedResourceData();
123
662
  const nextErrors = { ...data.errors };
124
663
  delete nextErrors[resourceKey];
125
664
  const nextData = {
126
665
  ...data,
127
- entities: {
128
- ...data.entities,
129
- [resourceKey]: entity
130
- },
131
- isLoading: {
132
- ...data.isLoading,
133
- [resourceKey]: false
134
- },
135
- status: {
136
- ...data.status,
137
- [resourceKey]: "Success"
138
- },
666
+ entities: { ...data.entities, [resourceKey]: entity },
667
+ isLoading: { ...data.isLoading, [resourceKey]: false },
668
+ status: { ...data.status, [resourceKey]: "Success" },
139
669
  errors: nextErrors
140
670
  };
141
- this.update(key, {
671
+ return applyUpdate(key, {
142
672
  data: nextData,
143
673
  isLoading: isAnyKeyLoading(nextData.isLoading),
144
674
  status: void 0,
145
675
  errors: void 0
146
676
  });
147
677
  }
148
- clearKeyedOne(key, resourceKey) {
149
- const currentState = this.signalsState.get(key.toString());
150
- if (!currentState) {
151
- return;
152
- }
153
- const previousState = currentState();
678
+ function applyClearKeyedOne(key, resourceKey) {
679
+ const sig = signals.getOrCreate(key);
680
+ const previousState = sig();
154
681
  const state = previousState;
155
682
  if (!isKeyedResourceData(state.data)) {
156
- return;
683
+ return true;
157
684
  }
158
685
  const data = state.data;
159
686
  const nextEntities = { ...data.entities };
@@ -171,8 +698,7 @@ var BaseStore = class {
171
698
  status: nextStatus,
172
699
  errors: nextErrors
173
700
  };
174
- const _typedKey = key;
175
- currentState.update(
701
+ sig.update(
176
702
  (prev) => ({
177
703
  ...prev,
178
704
  data: nextData,
@@ -181,19 +707,15 @@ var BaseStore = class {
181
707
  errors: void 0
182
708
  })
183
709
  );
184
- const updatedState = currentState();
185
- this.notifyUpdateHooks(key, updatedState, previousState);
710
+ const updatedState = sig();
711
+ notifier.notify(key, updatedState, previousState);
712
+ return true;
186
713
  }
187
- startKeyedLoading(key, resourceKey) {
188
- const currentState = this.signalsState.get(key.toString());
189
- if (!currentState) {
190
- return;
191
- }
192
- const _typedKey = key;
193
- const state = currentState();
714
+ function applyStartKeyedLoading(key, resourceKey) {
715
+ const sig = signals.getOrCreate(key);
716
+ const state = sig();
194
717
  if (!isKeyedResourceData(state.data)) {
195
- this.startLoading(key);
196
- return;
718
+ return applyStartLoading(key);
197
719
  }
198
720
  const previousState = state;
199
721
  const data = state.data;
@@ -201,13 +723,9 @@ var BaseStore = class {
201
723
  ...data.isLoading,
202
724
  [resourceKey]: true
203
725
  };
204
- const nextStatus = {
205
- ...data.status
206
- };
726
+ const nextStatus = { ...data.status };
207
727
  delete nextStatus[resourceKey];
208
- const nextErrors = {
209
- ...data.errors
210
- };
728
+ const nextErrors = { ...data.errors };
211
729
  delete nextErrors[resourceKey];
212
730
  const nextData = {
213
731
  ...data,
@@ -215,7 +733,7 @@ var BaseStore = class {
215
733
  status: nextStatus,
216
734
  errors: nextErrors
217
735
  };
218
- currentState.update(
736
+ sig.update(
219
737
  (previous) => ({
220
738
  ...previous,
221
739
  data: nextData,
@@ -224,8 +742,277 @@ var BaseStore = class {
224
742
  errors: void 0
225
743
  })
226
744
  );
227
- const updatedState = currentState();
228
- this.notifyUpdateHooks(key, updatedState, previousState);
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
+ };
834
+ }
835
+
836
+ // src/base-store.ts
837
+ var updateHooksMap = /* @__PURE__ */ new WeakMap();
838
+ var BaseStore = class {
839
+ constructor(storeEnum, options) {
840
+ this.storeEnum = storeEnum;
841
+ this.storeKeys = Object.keys(storeEnum);
842
+ this.initializeState();
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
+ });
859
+ trackStore(this);
860
+ }
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
+ }
896
+ /**
897
+ * Returns a **read-only** `Signal` for the given store slot.
898
+ *
899
+ * @param key - The slot name to read.
900
+ * @returns A `Signal` wrapping the slot's current {@link ResourceState}.
901
+ */
902
+ get(key) {
903
+ return this.signalsState.get(key.toString());
904
+ }
905
+ /**
906
+ * Registers a callback fired after every `update` or `clear` on the given slot.
907
+ *
908
+ * @param key - The slot to watch.
909
+ * @param callback - Receives the new state and the previous state.
910
+ * @returns A cleanup function that removes the listener when called.
911
+ */
912
+ onUpdate(key, callback) {
913
+ const hooks = updateHooksMap.get(this);
914
+ if (!hooks.has(key)) {
915
+ hooks.set(key, []);
916
+ }
917
+ hooks.get(key).push(
918
+ callback
919
+ );
920
+ return () => {
921
+ const hooksMap = hooks.get(key);
922
+ if (!hooksMap) {
923
+ return;
924
+ }
925
+ const index = hooksMap.indexOf(
926
+ callback
927
+ );
928
+ if (index > -1) {
929
+ hooksMap.splice(index, 1);
930
+ }
931
+ };
932
+ }
933
+ /**
934
+ * Partially updates a slot by merging `newState` into the current value (immutable spread).
935
+ *
936
+ * @param key - The slot to update.
937
+ * @param newState - Partial state to merge (e.g. `{ data: newData, status: 'Success' }`).
938
+ */
939
+ update(key, newState) {
940
+ this.history.publish(
941
+ createUpdateMessage(key, cloneValue(newState))
942
+ );
943
+ }
944
+ /** Resets every slot in this store to its initial idle state. */
945
+ clearAll() {
946
+ this.history.publish(createClearAllMessage());
947
+ }
948
+ /**
949
+ * Resets a single slot to `{ data: undefined, isLoading: false, status: undefined, errors: undefined }`.
950
+ *
951
+ * @param key - The slot to clear.
952
+ */
953
+ clear(key) {
954
+ this.history.publish(createClearMessage(key));
955
+ }
956
+ /**
957
+ * Marks a slot as loading: sets `isLoading: true` and clears `status` and `errors`.
958
+ *
959
+ * @param key - The slot to mark as loading.
960
+ */
961
+ startLoading(key) {
962
+ this.history.publish(createStartLoadingMessage(key));
963
+ }
964
+ /**
965
+ * Marks a slot as no longer loading: sets `isLoading: false`.
966
+ * Does **not** clear `status` or `errors`.
967
+ *
968
+ * @param key - The slot to stop loading.
969
+ */
970
+ stopLoading(key) {
971
+ this.history.publish(createStopLoadingMessage(key));
972
+ }
973
+ /**
974
+ * Merges a single entity into a {@link KeyedResourceData} slot.
975
+ * Sets its status to `'Success'` and clears per-key errors.
976
+ * The top-level `isLoading` is recalculated based on remaining loading keys.
977
+ *
978
+ * @param key - The keyed slot name.
979
+ * @param resourceKey - The entity identifier (e.g. `'inv-123'`).
980
+ * @param entity - The entity value to store.
981
+ */
982
+ updateKeyedOne(key, resourceKey, entity) {
983
+ this.history.publish(
984
+ createUpdateKeyedOneMessage(
985
+ key,
986
+ resourceKey,
987
+ cloneValue(entity)
988
+ )
989
+ );
990
+ }
991
+ /**
992
+ * Removes a single entity from a {@link KeyedResourceData} slot,
993
+ * including its loading flag, status, and errors.
994
+ * Recalculates the top-level `isLoading` from the remaining keys.
995
+ *
996
+ * @param key - The keyed slot name.
997
+ * @param resourceKey - The entity identifier to remove.
998
+ */
999
+ clearKeyedOne(key, resourceKey) {
1000
+ this.history.publish(
1001
+ createClearKeyedOneMessage(key, resourceKey)
1002
+ );
1003
+ }
1004
+ /**
1005
+ * Marks a single entity within a keyed slot as loading.
1006
+ * Clears its status and errors. If the slot data is not yet a {@link KeyedResourceData},
1007
+ * falls back to `startLoading(key)`.
1008
+ *
1009
+ * @param key - The keyed slot name.
1010
+ * @param resourceKey - The entity identifier to mark as loading.
1011
+ */
1012
+ startKeyedLoading(key, resourceKey) {
1013
+ this.history.publish(
1014
+ createStartKeyedLoadingMessage(key, resourceKey)
1015
+ );
229
1016
  }
230
1017
  notifyUpdateHooks(key, nextState, previousState) {
231
1018
  const hooks = updateHooksMap.get(this);
@@ -233,25 +1020,34 @@ var BaseStore = class {
233
1020
  if (!keyHooks) {
234
1021
  return;
235
1022
  }
236
- keyHooks.forEach(
237
- (hook) => hook(
238
- nextState,
239
- previousState
240
- )
241
- );
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
+ }
242
1045
  }
243
1046
  initializeState() {
244
- Object.keys(this.storeEnum).forEach((key) => {
245
- const _typedKey = key;
246
- const initialState = {
247
- data: void 0,
248
- isLoading: false,
249
- status: void 0,
250
- errors: void 0
251
- };
1047
+ this.storeKeys.forEach((key) => {
252
1048
  this.signalsState.set(
253
- _typedKey,
254
- signal(initialState)
1049
+ key,
1050
+ signal(createDefaultState())
255
1051
  );
256
1052
  });
257
1053
  }
@@ -259,23 +1055,58 @@ var BaseStore = class {
259
1055
 
260
1056
  // src/lazy-store.ts
261
1057
  import { signal as signal2 } from "@angular/core";
262
- import {
263
- isAnyKeyLoading as isAnyKeyLoading2,
264
- isKeyedResourceData as isKeyedResourceData2,
265
- createKeyedResourceData as createKeyedResourceData2
266
- } from "@flurryx/core";
267
- function createDefaultState() {
268
- return {
269
- data: void 0,
270
- isLoading: false,
271
- status: void 0,
272
- errors: void 0
273
- };
274
- }
275
1058
  var LazyStore = class {
276
1059
  signals = /* @__PURE__ */ new Map();
277
1060
  hooks = /* @__PURE__ */ new Map();
278
- 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
+ });
279
1110
  trackStore(this);
280
1111
  }
281
1112
  getOrCreate(key) {
@@ -286,138 +1117,55 @@ var LazyStore = class {
286
1117
  }
287
1118
  return sig;
288
1119
  }
1120
+ /** @inheritDoc */
289
1121
  get(key) {
290
1122
  return this.getOrCreate(key);
291
1123
  }
1124
+ /** @inheritDoc */
292
1125
  update(key, newState) {
293
- const sig = this.getOrCreate(key);
294
- const previousState = sig();
295
- sig.update((state) => ({ ...state, ...newState }));
296
- const nextState = sig();
297
- this.notifyHooks(key, nextState, previousState);
1126
+ this.history.publish(
1127
+ createUpdateMessage(key, cloneValue(newState))
1128
+ );
298
1129
  }
1130
+ /** @inheritDoc */
299
1131
  clear(key) {
300
- const sig = this.getOrCreate(key);
301
- const previousState = sig();
302
- sig.set(createDefaultState());
303
- const nextState = sig();
304
- this.notifyHooks(key, nextState, previousState);
1132
+ this.history.publish(createClearMessage(key));
305
1133
  }
1134
+ /** @inheritDoc */
306
1135
  clearAll() {
307
- for (const key of this.signals.keys()) {
308
- this.clear(key);
309
- }
1136
+ this.history.publish(createClearAllMessage());
310
1137
  }
1138
+ /** @inheritDoc */
311
1139
  startLoading(key) {
312
- const sig = this.getOrCreate(key);
313
- sig.update(
314
- (state) => ({
315
- ...state,
316
- status: void 0,
317
- isLoading: true,
318
- errors: void 0
319
- })
320
- );
1140
+ this.history.publish(createStartLoadingMessage(key));
321
1141
  }
1142
+ /** @inheritDoc */
322
1143
  stopLoading(key) {
323
- const sig = this.getOrCreate(key);
324
- sig.update(
325
- (state) => ({
326
- ...state,
327
- isLoading: false
328
- })
329
- );
1144
+ this.history.publish(createStopLoadingMessage(key));
330
1145
  }
1146
+ /** @inheritDoc */
331
1147
  updateKeyedOne(key, resourceKey, entity) {
332
- const sig = this.getOrCreate(key);
333
- const state = sig();
334
- const data = isKeyedResourceData2(state.data) ? state.data : createKeyedResourceData2();
335
- const nextErrors = { ...data.errors };
336
- delete nextErrors[resourceKey];
337
- const nextData = {
338
- ...data,
339
- entities: { ...data.entities, [resourceKey]: entity },
340
- isLoading: { ...data.isLoading, [resourceKey]: false },
341
- status: { ...data.status, [resourceKey]: "Success" },
342
- errors: nextErrors
343
- };
344
- this.update(key, {
345
- data: nextData,
346
- isLoading: isAnyKeyLoading2(nextData.isLoading),
347
- status: void 0,
348
- errors: void 0
349
- });
1148
+ this.history.publish(
1149
+ createUpdateKeyedOneMessage(
1150
+ key,
1151
+ resourceKey,
1152
+ cloneValue(entity)
1153
+ )
1154
+ );
350
1155
  }
1156
+ /** @inheritDoc */
351
1157
  clearKeyedOne(key, resourceKey) {
352
- const sig = this.getOrCreate(key);
353
- const state = sig();
354
- if (!isKeyedResourceData2(state.data)) {
355
- return;
356
- }
357
- const data = state.data;
358
- const previousState = state;
359
- const nextEntities = { ...data.entities };
360
- delete nextEntities[resourceKey];
361
- const nextIsLoading = { ...data.isLoading };
362
- delete nextIsLoading[resourceKey];
363
- const nextStatus = { ...data.status };
364
- delete nextStatus[resourceKey];
365
- const nextErrors = { ...data.errors };
366
- delete nextErrors[resourceKey];
367
- const nextData = {
368
- ...data,
369
- entities: nextEntities,
370
- isLoading: nextIsLoading,
371
- status: nextStatus,
372
- errors: nextErrors
373
- };
374
- sig.update(
375
- (prev) => ({
376
- ...prev,
377
- data: nextData,
378
- status: void 0,
379
- isLoading: isAnyKeyLoading2(nextIsLoading),
380
- errors: void 0
381
- })
1158
+ this.history.publish(
1159
+ createClearKeyedOneMessage(key, resourceKey)
382
1160
  );
383
- const updatedState = sig();
384
- this.notifyHooks(key, updatedState, previousState);
385
1161
  }
1162
+ /** @inheritDoc */
386
1163
  startKeyedLoading(key, resourceKey) {
387
- const sig = this.getOrCreate(key);
388
- const state = sig();
389
- if (!isKeyedResourceData2(state.data)) {
390
- this.startLoading(key);
391
- return;
392
- }
393
- const previousState = state;
394
- const data = state.data;
395
- const nextIsLoading = {
396
- ...data.isLoading,
397
- [resourceKey]: true
398
- };
399
- const nextStatus = { ...data.status };
400
- delete nextStatus[resourceKey];
401
- const nextErrors = { ...data.errors };
402
- delete nextErrors[resourceKey];
403
- const nextData = {
404
- ...data,
405
- isLoading: nextIsLoading,
406
- status: nextStatus,
407
- errors: nextErrors
408
- };
409
- sig.update(
410
- (previous) => ({
411
- ...previous,
412
- data: nextData,
413
- status: void 0,
414
- isLoading: isAnyKeyLoading2(nextIsLoading),
415
- errors: void 0
416
- })
1164
+ this.history.publish(
1165
+ createStartKeyedLoadingMessage(key, resourceKey)
417
1166
  );
418
- const updatedState = sig();
419
- this.notifyHooks(key, updatedState, previousState);
420
1167
  }
1168
+ /** @inheritDoc */
421
1169
  onUpdate(key, callback) {
422
1170
  if (!this.hooks.has(key)) {
423
1171
  this.hooks.set(key, []);
@@ -440,12 +1188,28 @@ var LazyStore = class {
440
1188
  if (!keyHooks) {
441
1189
  return;
442
1190
  }
443
- keyHooks.forEach(
444
- (hook) => hook(
445
- nextState,
446
- previousState
447
- )
448
- );
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
+ }
449
1213
  }
450
1214
  };
451
1215
 
@@ -454,12 +1218,12 @@ import { InjectionToken, inject } from "@angular/core";
454
1218
 
455
1219
  // src/dynamic-store.ts
456
1220
  var DynamicStore = class extends BaseStore {
457
- constructor(config) {
1221
+ constructor(config, options) {
458
1222
  const identityEnum = Object.keys(config).reduce(
459
1223
  (acc, key) => ({ ...acc, [key]: key }),
460
1224
  {}
461
1225
  );
462
- super(identityEnum);
1226
+ super(identityEnum, options);
463
1227
  }
464
1228
  };
465
1229
 
@@ -480,12 +1244,12 @@ function mirrorKey(source, sourceKey, target, targetKeyOrOptions, options) {
480
1244
  }
481
1245
 
482
1246
  // src/collect-keyed.ts
483
- import { createKeyedResourceData as createKeyedResourceData3, isAnyKeyLoading as isAnyKeyLoading3 } from "@flurryx/core";
1247
+ import { createKeyedResourceData as createKeyedResourceData2, isAnyKeyLoading as isAnyKeyLoading2 } from "@flurryx/core";
484
1248
  function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
485
1249
  const resolvedTargetKey = typeof targetKeyOrOptions === "string" ? targetKeyOrOptions : sourceKey;
486
1250
  const resolvedOptions = typeof targetKeyOrOptions === "object" ? targetKeyOrOptions : options;
487
1251
  target.update(resolvedTargetKey, {
488
- data: createKeyedResourceData3()
1252
+ data: createKeyedResourceData2()
489
1253
  });
490
1254
  let previousId;
491
1255
  const cleanup = source.onUpdate(sourceKey, (state) => {
@@ -497,9 +1261,15 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
497
1261
  return;
498
1262
  }
499
1263
  if (resourceState.status === "Success" && currentId !== void 0) {
500
- const newEntities = { ...currentKeyed.entities, [currentId]: resourceState.data };
1264
+ const newEntities = {
1265
+ ...currentKeyed.entities,
1266
+ [currentId]: resourceState.data
1267
+ };
501
1268
  const newIsLoading = { ...currentKeyed.isLoading, [currentId]: false };
502
- const newStatus = { ...currentKeyed.status, [currentId]: resourceState.status };
1269
+ const newStatus = {
1270
+ ...currentKeyed.status,
1271
+ [currentId]: resourceState.status
1272
+ };
503
1273
  const newErrors = { ...currentKeyed.errors };
504
1274
  delete newErrors[currentId];
505
1275
  const updatedKeyed = {
@@ -510,14 +1280,20 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
510
1280
  };
511
1281
  target.update(resolvedTargetKey, {
512
1282
  data: updatedKeyed,
513
- isLoading: isAnyKeyLoading3(newIsLoading),
1283
+ isLoading: isAnyKeyLoading2(newIsLoading),
514
1284
  status: "Success"
515
1285
  });
516
1286
  previousId = currentId;
517
1287
  } else if (resourceState.status === "Error" && currentId !== void 0) {
518
1288
  const newIsLoading = { ...currentKeyed.isLoading, [currentId]: false };
519
- const newStatus = { ...currentKeyed.status, [currentId]: resourceState.status };
520
- const newErrors = { ...currentKeyed.errors, [currentId]: resourceState.errors };
1289
+ const newStatus = {
1290
+ ...currentKeyed.status,
1291
+ [currentId]: resourceState.status
1292
+ };
1293
+ const newErrors = {
1294
+ ...currentKeyed.errors,
1295
+ [currentId]: resourceState.errors
1296
+ };
521
1297
  const updatedKeyed = {
522
1298
  entities: { ...currentKeyed.entities },
523
1299
  isLoading: newIsLoading,
@@ -526,7 +1302,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
526
1302
  };
527
1303
  target.update(resolvedTargetKey, {
528
1304
  data: updatedKeyed,
529
- isLoading: isAnyKeyLoading3(newIsLoading)
1305
+ isLoading: isAnyKeyLoading2(newIsLoading)
530
1306
  });
531
1307
  previousId = currentId;
532
1308
  } else if (resourceState.data === void 0 && previousId !== void 0) {
@@ -542,7 +1318,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
542
1318
  };
543
1319
  target.update(resolvedTargetKey, {
544
1320
  data: updatedKeyed,
545
- isLoading: isAnyKeyLoading3(remainingLoading)
1321
+ isLoading: isAnyKeyLoading2(remainingLoading)
546
1322
  });
547
1323
  previousId = void 0;
548
1324
  } else if (resourceState.isLoading && currentId !== void 0) {
@@ -582,7 +1358,6 @@ function wireMirrors(store, mirrors) {
582
1358
  def.targetKey
583
1359
  );
584
1360
  }
585
- return store;
586
1361
  }
587
1362
  function wireMirrorKeyed(store, defs) {
588
1363
  for (const def of defs) {
@@ -597,7 +1372,6 @@ function wireMirrorKeyed(store, defs) {
597
1372
  }
598
1373
  );
599
1374
  }
600
- return store;
601
1375
  }
602
1376
  var MIRROR_SELF_SAME_KEY_ERROR = "mirrorSelf source and target keys must be different";
603
1377
  function wireSelfMirrors(store, defs) {
@@ -612,7 +1386,6 @@ function wireSelfMirrors(store, defs) {
612
1386
  def.targetKey
613
1387
  );
614
1388
  }
615
- return store;
616
1389
  }
617
1390
  function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors = []) {
618
1391
  return {
@@ -669,11 +1442,11 @@ function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors =
669
1442
  selfMirrors
670
1443
  );
671
1444
  },
672
- build() {
1445
+ build(options) {
673
1446
  return new InjectionToken("FlurryxStore", {
674
1447
  providedIn: "root",
675
1448
  factory: () => {
676
- const store = new DynamicStore(accum);
1449
+ const store = new DynamicStore(accum, options);
677
1450
  wireMirrors(store, mirrors);
678
1451
  wireMirrorKeyed(store, mirrorKeyedDefs);
679
1452
  wireSelfMirrors(store, selfMirrors);
@@ -744,11 +1517,11 @@ function createConstrainedBuilder(_enumObj, accum, mirrors = [], mirrorKeyedDefs
744
1517
  selfMirrors
745
1518
  );
746
1519
  },
747
- build() {
1520
+ build(options) {
748
1521
  return new InjectionToken("FlurryxStore", {
749
1522
  providedIn: "root",
750
1523
  factory: () => {
751
- const store = new DynamicStore(accum);
1524
+ const store = new DynamicStore(accum, options);
752
1525
  wireMirrors(store, mirrors);
753
1526
  wireMirrorKeyed(store, mirrorKeyedDefs);
754
1527
  wireSelfMirrors(store, selfMirrors);
@@ -795,11 +1568,11 @@ function createInterfaceBuilder(mirrors = [], mirrorKeyedDefs = [], selfMirrors
795
1568
  selfMirrors
796
1569
  );
797
1570
  },
798
- build() {
1571
+ build(options) {
799
1572
  return new InjectionToken("FlurryxStore", {
800
1573
  providedIn: "root",
801
1574
  factory: () => {
802
- const store = new LazyStore();
1575
+ const store = new LazyStore(options);
803
1576
  wireMirrors(store, mirrors);
804
1577
  wireMirrorKeyed(store, mirrorKeyedDefs);
805
1578
  wireSelfMirrors(store, selfMirrors);
@@ -824,7 +1597,14 @@ export {
824
1597
  LazyStore,
825
1598
  Store,
826
1599
  clearAllStores,
1600
+ cloneValue,
827
1601
  collectKeyed,
1602
+ createCompositeStoreMessageChannel,
1603
+ createInMemoryStoreMessageChannel,
1604
+ createLocalStorageStoreMessageChannel,
1605
+ createSessionStorageStoreMessageChannel,
1606
+ createSnapshotRestorePatch,
1607
+ createStorageStoreMessageChannel,
828
1608
  mirrorKey
829
1609
  };
830
1610
  //# sourceMappingURL=index.js.map