@flurryx/store 0.8.4 → 1.0.1

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,1080 @@
1
1
  // src/base-store.ts
2
- import { signal } from "@angular/core";
3
- import {
4
- isAnyKeyLoading,
5
- isKeyedResourceData,
6
- createKeyedResourceData
7
- } from "@flurryx/core";
2
+ import { signal as signal2 } from "@angular/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-replay.ts
86
+ import { signal, computed } from "@angular/core";
87
+
88
+ // src/store-messages.ts
89
+ var INVALID_HISTORY_INDEX_ERROR = "History index is out of range";
90
+ var INVALID_HISTORY_MESSAGE_ID_ERROR = "History message id is out of range";
91
+ var MESSAGE_NOT_ACKNOWLEDGED_ERROR = "Message was not acknowledged";
92
+
93
+ // src/store-channels.ts
94
+ function serializeStoreMessageChannelValue(value) {
95
+ if (value === void 0) {
96
+ return { __flurryxType: "undefined" };
97
+ }
98
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
99
+ return value;
100
+ }
101
+ if (value instanceof Date) {
102
+ return {
103
+ __flurryxType: "date",
104
+ value: value.toISOString()
105
+ };
106
+ }
107
+ if (value instanceof Map) {
108
+ return {
109
+ __flurryxType: "map",
110
+ entries: Array.from(value.entries(), ([key, entryValue]) => [
111
+ serializeStoreMessageChannelValue(key),
112
+ serializeStoreMessageChannelValue(entryValue)
113
+ ])
114
+ };
115
+ }
116
+ if (value instanceof Set) {
117
+ return {
118
+ __flurryxType: "set",
119
+ values: Array.from(
120
+ value.values(),
121
+ (entryValue) => serializeStoreMessageChannelValue(entryValue)
122
+ )
123
+ };
124
+ }
125
+ if (Array.isArray(value)) {
126
+ return {
127
+ __flurryxType: "array",
128
+ values: value.map(
129
+ (entryValue) => serializeStoreMessageChannelValue(entryValue)
130
+ )
131
+ };
132
+ }
133
+ if (typeof value === "object") {
134
+ return {
135
+ __flurryxType: "object",
136
+ entries: Object.entries(value).map(
137
+ ([key, entryValue]) => [
138
+ key,
139
+ serializeStoreMessageChannelValue(entryValue)
140
+ ]
141
+ )
142
+ };
143
+ }
144
+ throw new Error("Store message channel cannot serialize this value");
145
+ }
146
+ function deserializeStoreMessageChannelValue(value) {
147
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
148
+ return value;
149
+ }
150
+ switch (value.__flurryxType) {
151
+ case "undefined":
152
+ return void 0;
153
+ case "date":
154
+ return new Date(value.value);
155
+ case "array":
156
+ return value.values.map(
157
+ (entryValue) => deserializeStoreMessageChannelValue(entryValue)
158
+ );
159
+ case "set":
160
+ return new Set(
161
+ value.values.map(
162
+ (entryValue) => deserializeStoreMessageChannelValue(entryValue)
163
+ )
164
+ );
165
+ case "map":
166
+ return new Map(
167
+ value.entries.map(([key, entryValue]) => [
168
+ deserializeStoreMessageChannelValue(key),
169
+ deserializeStoreMessageChannelValue(entryValue)
170
+ ])
171
+ );
172
+ case "object": {
173
+ const result = {};
174
+ value.entries.forEach(([key, entryValue]) => {
175
+ result[key] = deserializeStoreMessageChannelValue(entryValue);
176
+ });
177
+ return result;
178
+ }
179
+ }
180
+ }
181
+ function defaultSerializeStoreMessageChannelState(state) {
182
+ return JSON.stringify(serializeStoreMessageChannelValue(state));
183
+ }
184
+ function defaultDeserializeStoreMessageChannelState(value) {
185
+ return deserializeStoreMessageChannelValue(
186
+ JSON.parse(value)
187
+ );
188
+ }
189
+ function normalizeStoreMessageChannelState(state) {
190
+ const maxId = state.messages.reduce(
191
+ (currentMax, entry) => Math.max(currentMax, entry.id),
192
+ 0
193
+ );
194
+ return {
195
+ nextId: Math.max(state.nextId, maxId + 1),
196
+ messages: state.messages.map((entry) => cloneValue(entry))
197
+ };
198
+ }
199
+ function createInitialStoreMessageRecord(id, message, clock = Date.now) {
200
+ return {
201
+ id,
202
+ message: cloneValue(message),
203
+ status: "pending",
204
+ attempts: 0,
205
+ createdAt: clock(),
206
+ lastAttemptedAt: null,
207
+ acknowledgedAt: null,
208
+ error: null
209
+ };
210
+ }
211
+ function isQuotaExceededError(error) {
212
+ return error instanceof DOMException && (error.code === 22 || error.code === 1014 || error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED");
213
+ }
214
+ function resolveGlobalStorage(name) {
215
+ const storage = globalThis[name];
216
+ if (!storage) {
217
+ throw new Error(`${name} is not available in this environment`);
218
+ }
219
+ return storage;
220
+ }
221
+ function createInMemoryStoreMessageChannel() {
222
+ let messages = [];
223
+ let nextId = 1;
224
+ return {
225
+ publish(message) {
226
+ const record = createInitialStoreMessageRecord(nextId++, message);
227
+ messages = [...messages, record];
228
+ return cloneValue(record);
229
+ },
230
+ getMessage(id) {
231
+ const record = messages.find((entry) => entry.id === id);
232
+ return record ? cloneValue(record) : void 0;
233
+ },
234
+ getMessages() {
235
+ return messages.map((entry) => cloneValue(entry));
236
+ },
237
+ saveMessage(entry) {
238
+ const record = cloneValue(entry);
239
+ const existingIndex = messages.findIndex(
240
+ (candidate) => candidate.id === record.id
241
+ );
242
+ if (existingIndex === -1) {
243
+ messages = [...messages, record];
244
+ } else {
245
+ messages = messages.map((c, i) => i === existingIndex ? record : c);
246
+ }
247
+ nextId = Math.max(nextId, record.id + 1);
248
+ }
249
+ };
250
+ }
251
+ function createStorageStoreMessageChannel(options) {
252
+ const serialize = options.serialize ?? defaultSerializeStoreMessageChannelState;
253
+ const deserialize = options.deserialize ?? defaultDeserializeStoreMessageChannelState;
254
+ function readState() {
255
+ const rawState = options.storage.getItem(options.storageKey);
256
+ if (rawState === null) {
257
+ return { nextId: 1, messages: [] };
258
+ }
259
+ try {
260
+ return normalizeStoreMessageChannelState(deserialize(rawState));
261
+ } catch {
262
+ return { nextId: 1, messages: [] };
263
+ }
264
+ }
265
+ function writeState(state) {
266
+ const normalized = normalizeStoreMessageChannelState(state);
267
+ try {
268
+ options.storage.setItem(options.storageKey, serialize(normalized));
269
+ } catch (error) {
270
+ if (!isQuotaExceededError(error) || normalized.messages.length === 0) {
271
+ throw error;
272
+ }
273
+ let remaining = [...normalized.messages];
274
+ while (remaining.length > 0) {
275
+ remaining = remaining.slice(1);
276
+ try {
277
+ options.storage.setItem(
278
+ options.storageKey,
279
+ serialize({ nextId: normalized.nextId, messages: remaining })
280
+ );
281
+ return;
282
+ } catch (retryError) {
283
+ if (!isQuotaExceededError(retryError)) {
284
+ throw retryError;
285
+ }
286
+ }
287
+ }
288
+ options.storage.setItem(
289
+ options.storageKey,
290
+ serialize({ nextId: normalized.nextId, messages: [] })
291
+ );
292
+ }
293
+ }
294
+ return {
295
+ publish(message) {
296
+ const state = readState();
297
+ const record = createInitialStoreMessageRecord(state.nextId, message);
298
+ writeState({
299
+ nextId: state.nextId + 1,
300
+ messages: [...state.messages, record]
301
+ });
302
+ return cloneValue(record);
303
+ },
304
+ getMessage(id) {
305
+ const record = readState().messages.find((entry) => entry.id === id);
306
+ return record ? cloneValue(record) : void 0;
307
+ },
308
+ getMessages() {
309
+ return readState().messages.map((entry) => cloneValue(entry));
310
+ },
311
+ saveMessage(entry) {
312
+ const state = readState();
313
+ const record = cloneValue(entry);
314
+ const existingIndex = state.messages.findIndex(
315
+ (candidate) => candidate.id === record.id
316
+ );
317
+ if (existingIndex === -1) {
318
+ writeState({
319
+ nextId: Math.max(state.nextId, record.id + 1),
320
+ messages: [...state.messages, record]
321
+ });
322
+ return;
323
+ }
324
+ const nextMessages = state.messages.map(
325
+ (c, i) => i === existingIndex ? record : c
326
+ );
327
+ writeState({
328
+ nextId: Math.max(state.nextId, record.id + 1),
329
+ messages: nextMessages
330
+ });
331
+ }
332
+ };
333
+ }
334
+ function createLocalStorageStoreMessageChannel(options) {
335
+ return createStorageStoreMessageChannel({
336
+ ...options,
337
+ storage: options.storage ?? resolveGlobalStorage("localStorage")
338
+ });
339
+ }
340
+ function createSessionStorageStoreMessageChannel(options) {
341
+ return createStorageStoreMessageChannel({
342
+ ...options,
343
+ storage: options.storage ?? resolveGlobalStorage("sessionStorage")
344
+ });
345
+ }
346
+ function createCompositeStoreMessageChannel(options) {
347
+ if (options.channels.length === 0) {
348
+ throw new Error(
349
+ "createCompositeStoreMessageChannel: 'channels' option must contain at least one channel"
350
+ );
351
+ }
352
+ const primaryChannel = options.channels[0];
353
+ const replicaChannels = options.channels.slice(1);
354
+ return {
355
+ publish(message) {
356
+ const record = primaryChannel.publish(message);
357
+ replicaChannels.forEach((channel) => {
358
+ channel.saveMessage(record);
359
+ });
360
+ return cloneValue(record);
361
+ },
362
+ getMessage(id) {
363
+ const record = primaryChannel.getMessage(id);
364
+ return record ? cloneValue(record) : void 0;
365
+ },
366
+ getMessages() {
367
+ return primaryChannel.getMessages().map((record) => cloneValue(record));
368
+ },
369
+ saveMessage(entry) {
370
+ primaryChannel.saveMessage(entry);
371
+ replicaChannels.forEach((channel) => {
372
+ channel.saveMessage(entry);
373
+ });
374
+ }
375
+ };
376
+ }
377
+
378
+ // src/store-replay.ts
379
+ function messageAffectsKey(message, key) {
380
+ if (message.type === "clearAll") {
381
+ return true;
382
+ }
383
+ return "key" in message && message.key === key;
384
+ }
385
+ function toDeadLetterEntry(record) {
386
+ return {
387
+ id: record.id,
388
+ message: cloneValue(record.message),
389
+ attempts: record.attempts,
390
+ error: record.error ?? MESSAGE_NOT_ACKNOWLEDGED_ERROR,
391
+ failedAt: record.lastAttemptedAt ?? record.createdAt
392
+ };
393
+ }
394
+ function areValuesEquivalent(left, right, seen = /* @__PURE__ */ new WeakMap()) {
395
+ if (Object.is(left, right)) {
396
+ return true;
397
+ }
398
+ if (typeof left !== typeof right || left === null || right === null) {
399
+ return false;
400
+ }
401
+ if (typeof left !== "object" || typeof right !== "object") {
402
+ return false;
403
+ }
404
+ let seenRights = seen.get(left);
405
+ if (seenRights?.has(right)) {
406
+ return true;
407
+ }
408
+ if (!seenRights) {
409
+ seenRights = /* @__PURE__ */ new WeakSet();
410
+ seen.set(left, seenRights);
411
+ }
412
+ seenRights.add(right);
413
+ if (left instanceof Date || right instanceof Date) {
414
+ return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
415
+ }
416
+ if (left instanceof Map || right instanceof Map) {
417
+ if (!(left instanceof Map) || !(right instanceof Map)) {
418
+ return false;
419
+ }
420
+ const leftEntries = Array.from(left.entries());
421
+ const rightEntries = Array.from(right.entries());
422
+ if (leftEntries.length !== rightEntries.length) {
423
+ return false;
424
+ }
425
+ return leftEntries.every(([leftKey, leftValue], index) => {
426
+ const rightEntry = rightEntries[index];
427
+ if (!rightEntry) {
428
+ return false;
429
+ }
430
+ return areValuesEquivalent(leftKey, rightEntry[0], seen) && areValuesEquivalent(leftValue, rightEntry[1], seen);
431
+ });
432
+ }
433
+ if (left instanceof Set || right instanceof Set) {
434
+ if (!(left instanceof Set) || !(right instanceof Set)) {
435
+ return false;
436
+ }
437
+ const leftValues = Array.from(left.values());
438
+ const rightValues = Array.from(right.values());
439
+ if (leftValues.length !== rightValues.length) {
440
+ return false;
441
+ }
442
+ return leftValues.every(
443
+ (leftValue, index) => areValuesEquivalent(leftValue, rightValues[index], seen)
444
+ );
445
+ }
446
+ if (Array.isArray(left) || Array.isArray(right)) {
447
+ if (!Array.isArray(left) || !Array.isArray(right)) {
448
+ return false;
449
+ }
450
+ if (left.length !== right.length) {
451
+ return false;
452
+ }
453
+ return left.every(
454
+ (leftValue, index) => areValuesEquivalent(leftValue, right[index], seen)
455
+ );
456
+ }
457
+ if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) {
458
+ return false;
459
+ }
460
+ const leftRecord = left;
461
+ const rightRecord = right;
462
+ const leftKeys = Reflect.ownKeys(leftRecord);
463
+ const rightKeys = Reflect.ownKeys(rightRecord);
464
+ if (leftKeys.length !== rightKeys.length) {
465
+ return false;
466
+ }
467
+ return leftKeys.every((key) => {
468
+ if (!Object.prototype.hasOwnProperty.call(rightRecord, key)) {
469
+ return false;
470
+ }
471
+ return areValuesEquivalent(leftRecord[key], rightRecord[key], seen);
472
+ });
473
+ }
474
+ function areStoreMessageRecordsEquivalent(sourceRecord, cachedRecord) {
475
+ return sourceRecord.id === cachedRecord.id && sourceRecord.status === cachedRecord.status && sourceRecord.attempts === cachedRecord.attempts && sourceRecord.createdAt === cachedRecord.createdAt && sourceRecord.lastAttemptedAt === cachedRecord.lastAttemptedAt && sourceRecord.acknowledgedAt === cachedRecord.acknowledgedAt && sourceRecord.error === cachedRecord.error && areValuesEquivalent(sourceRecord.message, cachedRecord.message);
476
+ }
477
+ function createStableReadonlyCollection(items) {
478
+ return Object.freeze([...items]);
479
+ }
480
+ function appendStableReadonlyCollectionItem(input) {
481
+ return createStableReadonlyCollection([...input.items, input.item]);
482
+ }
483
+ function upsertStableReadonlyCollectionItem(input) {
484
+ const existingIndex = input.items.findIndex(
485
+ (candidate) => candidate.id === input.item.id
486
+ );
487
+ if (existingIndex === -1) {
488
+ return appendStableReadonlyCollectionItem(input);
489
+ }
490
+ if (Object.is(input.items[existingIndex], input.item)) {
491
+ return input.items;
492
+ }
493
+ const nextItems = [...input.items];
494
+ nextItems[existingIndex] = input.item;
495
+ return createStableReadonlyCollection(nextItems);
496
+ }
497
+ function syncStableReadonlyCollectionById(input) {
498
+ const cachedItemsById = /* @__PURE__ */ new Map();
499
+ input.items.forEach((item) => {
500
+ cachedItemsById.set(item.id, item);
501
+ });
502
+ let didChange = input.items.length !== input.sourceItems.length;
503
+ const nextItems = input.sourceItems.map((sourceItem, index) => {
504
+ const cachedItem = cachedItemsById.get(input.getSourceId(sourceItem));
505
+ const nextItem = cachedItem && input.areEquivalent(sourceItem, cachedItem) ? cachedItem : input.createItem(sourceItem);
506
+ if (!didChange && input.items[index] !== nextItem) {
507
+ didChange = true;
508
+ }
509
+ return nextItem;
510
+ });
511
+ return didChange ? createStableReadonlyCollection(nextItems) : input.items;
512
+ }
513
+ function createStoreHistory(config) {
514
+ const messageChannel = config.channel ?? createInMemoryStoreMessageChannel();
515
+ const clock = config.clock ?? Date.now;
516
+ let history = [
517
+ {
518
+ id: null,
519
+ index: 0,
520
+ message: null,
521
+ snapshot: config.captureSnapshot(),
522
+ acknowledgedAt: null
523
+ }
524
+ ];
525
+ let currentIndex = 0;
526
+ let historyCollection = createStableReadonlyCollection(
527
+ history.map((entry) => cloneValue(entry))
528
+ );
529
+ let messageCollection = createStableReadonlyCollection(
530
+ messageChannel.getMessages().map((record) => cloneValue(record))
531
+ );
532
+ const version = signal(0);
533
+ function notifyVersion() {
534
+ version.update((v) => v + 1);
535
+ }
536
+ function recordSnapshot(record) {
537
+ const nextIndex = history.length;
538
+ const nextHistoryEntry = {
539
+ id: record.id,
540
+ index: nextIndex,
541
+ message: cloneValue(record.message),
542
+ snapshot: config.captureSnapshot(),
543
+ acknowledgedAt: record.acknowledgedAt
544
+ };
545
+ history = [...history, nextHistoryEntry];
546
+ historyCollection = appendStableReadonlyCollectionItem({
547
+ items: historyCollection,
548
+ item: cloneValue(nextHistoryEntry)
549
+ });
550
+ currentIndex = nextIndex;
551
+ }
552
+ function truncateFutureHistory() {
553
+ if (currentIndex === history.length - 1) {
554
+ return;
555
+ }
556
+ history = history.slice(0, currentIndex + 1);
557
+ historyCollection = createStableReadonlyCollection(
558
+ historyCollection.slice(0, currentIndex + 1)
559
+ );
560
+ }
561
+ function ensureIndexInRange(index) {
562
+ if (!Number.isInteger(index) || index < 0 || index >= history.length) {
563
+ throw new Error(INVALID_HISTORY_INDEX_ERROR);
564
+ }
565
+ }
566
+ function travelTo(index) {
567
+ ensureIndexInRange(index);
568
+ config.applySnapshot(history[index].snapshot);
569
+ currentIndex = index;
570
+ notifyVersion();
571
+ }
572
+ function undo() {
573
+ if (currentIndex === 0) {
574
+ return false;
575
+ }
576
+ travelTo(currentIndex - 1);
577
+ return true;
578
+ }
579
+ function redo() {
580
+ if (currentIndex >= history.length - 1) {
581
+ return false;
582
+ }
583
+ travelTo(currentIndex + 1);
584
+ return true;
585
+ }
586
+ function getErrorMessage(error) {
587
+ if (error instanceof Error && error.message) {
588
+ return error.message;
589
+ }
590
+ return MESSAGE_NOT_ACKNOWLEDGED_ERROR;
591
+ }
592
+ function persistMessageAttempt(record, status, error, attemptedAt) {
593
+ const nextRecord = {
594
+ ...record,
595
+ message: cloneValue(record.message),
596
+ status,
597
+ attempts: record.attempts + 1,
598
+ lastAttemptedAt: attemptedAt,
599
+ acknowledgedAt: status === "acknowledged" ? attemptedAt : record.acknowledgedAt,
600
+ error
601
+ };
602
+ messageChannel.saveMessage(nextRecord);
603
+ messageCollection = upsertStableReadonlyCollectionItem({
604
+ items: messageCollection,
605
+ item: cloneValue(nextRecord)
606
+ });
607
+ return nextRecord;
608
+ }
609
+ function consumeRecord(record, options) {
610
+ const clonedMessage = cloneValue(record.message);
611
+ const attemptedAt = clock();
612
+ try {
613
+ const acknowledged = config.applyMessage(clonedMessage);
614
+ if (!acknowledged) {
615
+ throw new Error(MESSAGE_NOT_ACKNOWLEDGED_ERROR);
616
+ }
617
+ const acknowledgedRecord = persistMessageAttempt(
618
+ {
619
+ ...record,
620
+ message: clonedMessage
621
+ },
622
+ "acknowledged",
623
+ null,
624
+ attemptedAt
625
+ );
626
+ if (options?.recordHistory !== false) {
627
+ truncateFutureHistory();
628
+ recordSnapshot(acknowledgedRecord);
629
+ }
630
+ notifyVersion();
631
+ return true;
632
+ } catch (error) {
633
+ persistMessageAttempt(
634
+ {
635
+ ...record,
636
+ message: clonedMessage
637
+ },
638
+ "dead-letter",
639
+ getErrorMessage(error),
640
+ attemptedAt
641
+ );
642
+ notifyVersion();
643
+ return false;
644
+ }
645
+ }
646
+ function resolveReplayRecords(ids) {
647
+ return ids.map((id) => {
648
+ if (!Number.isInteger(id) || id < 1) {
649
+ throw new Error(INVALID_HISTORY_MESSAGE_ID_ERROR);
650
+ }
651
+ const record = messageChannel.getMessage(id);
652
+ if (!record) {
653
+ throw new Error(INVALID_HISTORY_MESSAGE_ID_ERROR);
654
+ }
655
+ return cloneValue(record);
656
+ });
657
+ }
658
+ function replayByIds(input) {
659
+ const ids = Array.isArray(input) ? input : [input];
660
+ const records = resolveReplayRecords(ids);
661
+ let acknowledgedCount = 0;
662
+ records.forEach((record) => {
663
+ if (consumeRecord(record)) {
664
+ acknowledgedCount += 1;
665
+ }
666
+ });
667
+ return acknowledgedCount;
668
+ }
669
+ function replayDeadLetter(id) {
670
+ const record = messageChannel.getMessage(id);
671
+ if (!record || record.status !== "dead-letter") {
672
+ return false;
673
+ }
674
+ return consumeRecord(record);
675
+ }
676
+ function replayDeadLetters() {
677
+ const ids = messageChannel.getMessages().filter((record) => record.status === "dead-letter").map((record) => record.id);
678
+ let acknowledgedCount = 0;
679
+ ids.forEach((id) => {
680
+ if (replayDeadLetter(id)) {
681
+ acknowledgedCount += 1;
682
+ }
683
+ });
684
+ return acknowledgedCount;
685
+ }
686
+ const historySignal = computed(() => {
687
+ version();
688
+ return historyCollection;
689
+ });
690
+ const messagesSignal = computed(() => {
691
+ version();
692
+ messageCollection = syncStableReadonlyCollectionById({
693
+ items: messageCollection,
694
+ sourceItems: messageChannel.getMessages(),
695
+ getSourceId: (record) => record.id,
696
+ createItem: (record) => cloneValue(record),
697
+ areEquivalent: areStoreMessageRecordsEquivalent
698
+ });
699
+ return messageCollection;
700
+ });
701
+ const currentIndexSignal = computed(() => {
702
+ version();
703
+ return currentIndex;
704
+ });
705
+ return {
706
+ historySignal,
707
+ messagesSignal,
708
+ currentIndexSignal,
709
+ publish(message) {
710
+ const record = messageChannel.publish(message);
711
+ messageCollection = appendStableReadonlyCollectionItem({
712
+ items: messageCollection,
713
+ item: cloneValue(record)
714
+ });
715
+ return consumeRecord(record);
716
+ },
717
+ replay(input) {
718
+ return replayByIds(input);
719
+ },
720
+ travelTo,
721
+ undo,
722
+ redo,
723
+ getHistory(key) {
724
+ if (key === void 0) {
725
+ return history.map((entry) => cloneValue(entry));
726
+ }
727
+ return history.filter((entry) => {
728
+ if (entry.message === null) {
729
+ return true;
730
+ }
731
+ return messageAffectsKey(entry.message, key);
732
+ }).map((entry) => cloneValue(entry));
733
+ },
734
+ getMessages(key) {
735
+ const records = messageChannel.getMessages();
736
+ if (key === void 0) {
737
+ return records.map((record) => cloneValue(record));
738
+ }
739
+ return records.filter((record) => messageAffectsKey(record.message, key)).map((record) => cloneValue(record));
740
+ },
741
+ getDeadLetters() {
742
+ return messageChannel.getMessages().filter((record) => record.status === "dead-letter").map((record) => toDeadLetterEntry(record));
743
+ },
744
+ replayDeadLetter,
745
+ replayDeadLetters,
746
+ getCurrentIndex() {
747
+ return currentIndex;
748
+ }
749
+ };
750
+ }
8
751
 
9
752
  // src/store-registry.ts
10
753
  var trackedStores = /* @__PURE__ */ new Set();
11
754
  function trackStore(store) {
12
755
  trackedStores.add(store);
13
756
  }
14
- function clearAllStores() {
15
- for (const store of [...trackedStores]) {
16
- store.clearAll();
17
- }
757
+ function clearAllStores() {
758
+ for (const store of [...trackedStores]) {
759
+ store.clearAll();
760
+ }
761
+ }
762
+
763
+ // src/store-message-consumer.ts
764
+ import {
765
+ isKeyedResourceData,
766
+ createKeyedResourceData,
767
+ isAnyKeyLoading
768
+ } from "@flurryx/core";
769
+ function createDefaultState() {
770
+ return {
771
+ data: void 0,
772
+ isLoading: false,
773
+ status: void 0,
774
+ errors: void 0
775
+ };
776
+ }
777
+ function createStoreMessageConsumer(signals, notifier) {
778
+ function applyUpdate(key, newState, notify = true) {
779
+ const sig = signals.getOrCreate(key);
780
+ const previousState = sig();
781
+ sig.update((state) => ({ ...state, ...newState }));
782
+ if (notify) {
783
+ const updatedState = sig();
784
+ notifier.notify(key, updatedState, previousState);
785
+ }
786
+ return true;
787
+ }
788
+ function applyClear(key) {
789
+ const sig = signals.getOrCreate(key);
790
+ const previousState = sig();
791
+ sig.set(createDefaultState());
792
+ const nextState = sig();
793
+ notifier.notify(key, nextState, previousState);
794
+ return true;
795
+ }
796
+ function applyClearAll() {
797
+ const keys = Array.from(signals.getAllKeys());
798
+ if (keys.length === 0) {
799
+ return false;
800
+ }
801
+ keys.forEach((key) => {
802
+ applyClear(key);
803
+ });
804
+ return true;
805
+ }
806
+ function applyStartLoading(key) {
807
+ const sig = signals.getOrCreate(key);
808
+ sig.update(
809
+ (state) => ({
810
+ ...state,
811
+ status: void 0,
812
+ isLoading: true,
813
+ errors: void 0
814
+ })
815
+ );
816
+ return true;
817
+ }
818
+ function applyStopLoading(key) {
819
+ const sig = signals.getOrCreate(key);
820
+ sig.update(
821
+ (state) => ({
822
+ ...state,
823
+ isLoading: false
824
+ })
825
+ );
826
+ return true;
827
+ }
828
+ function applyUpdateKeyedOne(key, resourceKey, entity) {
829
+ const sig = signals.getOrCreate(key);
830
+ const state = sig();
831
+ const data = isKeyedResourceData(state.data) ? state.data : createKeyedResourceData();
832
+ const nextErrors = { ...data.errors };
833
+ delete nextErrors[resourceKey];
834
+ const nextData = {
835
+ ...data,
836
+ entities: { ...data.entities, [resourceKey]: entity },
837
+ isLoading: { ...data.isLoading, [resourceKey]: false },
838
+ status: { ...data.status, [resourceKey]: "Success" },
839
+ errors: nextErrors
840
+ };
841
+ return applyUpdate(key, {
842
+ data: nextData,
843
+ isLoading: isAnyKeyLoading(nextData.isLoading),
844
+ status: void 0,
845
+ errors: void 0
846
+ });
847
+ }
848
+ function applyClearKeyedOne(key, resourceKey) {
849
+ const sig = signals.getOrCreate(key);
850
+ const previousState = sig();
851
+ const state = previousState;
852
+ if (!isKeyedResourceData(state.data)) {
853
+ return true;
854
+ }
855
+ const data = state.data;
856
+ const nextEntities = { ...data.entities };
857
+ delete nextEntities[resourceKey];
858
+ const nextIsLoading = { ...data.isLoading };
859
+ delete nextIsLoading[resourceKey];
860
+ const nextStatus = { ...data.status };
861
+ delete nextStatus[resourceKey];
862
+ const nextErrors = { ...data.errors };
863
+ delete nextErrors[resourceKey];
864
+ const nextData = {
865
+ ...data,
866
+ entities: nextEntities,
867
+ isLoading: nextIsLoading,
868
+ status: nextStatus,
869
+ errors: nextErrors
870
+ };
871
+ sig.update(
872
+ (prev) => ({
873
+ ...prev,
874
+ data: nextData,
875
+ status: void 0,
876
+ isLoading: isAnyKeyLoading(nextIsLoading),
877
+ errors: void 0
878
+ })
879
+ );
880
+ const updatedState = sig();
881
+ notifier.notify(key, updatedState, previousState);
882
+ return true;
883
+ }
884
+ function applyStartKeyedLoading(key, resourceKey) {
885
+ const sig = signals.getOrCreate(key);
886
+ const state = sig();
887
+ if (!isKeyedResourceData(state.data)) {
888
+ return applyStartLoading(key);
889
+ }
890
+ const previousState = state;
891
+ const data = state.data;
892
+ const nextIsLoading = {
893
+ ...data.isLoading,
894
+ [resourceKey]: true
895
+ };
896
+ const nextStatus = { ...data.status };
897
+ delete nextStatus[resourceKey];
898
+ const nextErrors = { ...data.errors };
899
+ delete nextErrors[resourceKey];
900
+ const nextData = {
901
+ ...data,
902
+ isLoading: nextIsLoading,
903
+ status: nextStatus,
904
+ errors: nextErrors
905
+ };
906
+ sig.update(
907
+ (previous) => ({
908
+ ...previous,
909
+ data: nextData,
910
+ status: void 0,
911
+ isLoading: isAnyKeyLoading(nextIsLoading),
912
+ errors: void 0
913
+ })
914
+ );
915
+ const updatedState = sig();
916
+ notifier.notify(key, updatedState, previousState);
917
+ return true;
918
+ }
919
+ function applyMessage(message) {
920
+ switch (message.type) {
921
+ case "update":
922
+ return applyUpdate(message.key, cloneValue(message.state));
923
+ case "clear":
924
+ return applyClear(message.key);
925
+ case "clearAll":
926
+ return applyClearAll();
927
+ case "startLoading":
928
+ return applyStartLoading(message.key);
929
+ case "stopLoading":
930
+ return applyStopLoading(message.key);
931
+ case "updateKeyedOne":
932
+ return applyUpdateKeyedOne(
933
+ message.key,
934
+ message.resourceKey,
935
+ cloneValue(message.entity)
936
+ );
937
+ case "clearKeyedOne":
938
+ return applyClearKeyedOne(message.key, message.resourceKey);
939
+ case "startKeyedLoading":
940
+ return applyStartKeyedLoading(message.key, message.resourceKey);
941
+ }
942
+ }
943
+ function applySnapshot(snapshot) {
944
+ const keys = /* @__PURE__ */ new Set([
945
+ ...Array.from(signals.getAllKeys()),
946
+ ...Object.keys(snapshot)
947
+ ]);
948
+ keys.forEach((rawKey) => {
949
+ const key = rawKey;
950
+ const sig = signals.getOrCreate(key);
951
+ const snapshotState = snapshot[key] ?? createDefaultState();
952
+ applyUpdate(key, createSnapshotRestorePatch(sig(), snapshotState), true);
953
+ });
954
+ }
955
+ function captureSnapshot() {
956
+ const entries = Array.from(signals.getAllKeys()).map((key) => [
957
+ key,
958
+ cloneValue(signals.getOrCreate(key)())
959
+ ]);
960
+ return Object.fromEntries(entries);
961
+ }
962
+ return {
963
+ applyMessage,
964
+ applySnapshot,
965
+ createSnapshot: captureSnapshot
966
+ };
967
+ }
968
+ function createUpdateMessage(key, state) {
969
+ return { type: "update", key, state };
970
+ }
971
+ function createClearMessage(key) {
972
+ return { type: "clear", key };
973
+ }
974
+ function createClearAllMessage() {
975
+ return { type: "clearAll" };
976
+ }
977
+ function createStartLoadingMessage(key) {
978
+ return { type: "startLoading", key };
979
+ }
980
+ function createStopLoadingMessage(key) {
981
+ return { type: "stopLoading", key };
982
+ }
983
+ function createUpdateKeyedOneMessage(key, resourceKey, entity) {
984
+ return {
985
+ type: "updateKeyedOne",
986
+ key,
987
+ resourceKey,
988
+ entity
989
+ };
990
+ }
991
+ function createClearKeyedOneMessage(key, resourceKey) {
992
+ return {
993
+ type: "clearKeyedOne",
994
+ key,
995
+ resourceKey
996
+ };
997
+ }
998
+ function createStartKeyedLoadingMessage(key, resourceKey) {
999
+ return {
1000
+ type: "startKeyedLoading",
1001
+ key,
1002
+ resourceKey
1003
+ };
18
1004
  }
19
1005
 
20
1006
  // src/base-store.ts
21
1007
  var updateHooksMap = /* @__PURE__ */ new WeakMap();
22
1008
  var BaseStore = class {
23
- constructor(storeEnum) {
1009
+ constructor(storeEnum, options) {
24
1010
  this.storeEnum = storeEnum;
1011
+ this.storeKeys = Object.keys(storeEnum);
25
1012
  this.initializeState();
26
1013
  updateHooksMap.set(this, /* @__PURE__ */ new Map());
1014
+ const consumer = createStoreMessageConsumer(
1015
+ {
1016
+ getOrCreate: (key) => this.signalsState.get(key),
1017
+ getAllKeys: () => this.storeKeys
1018
+ },
1019
+ {
1020
+ notify: (key, next, prev) => this.notifyUpdateHooks(key, next, prev)
1021
+ }
1022
+ );
1023
+ this.historyDriver = createStoreHistory({
1024
+ captureSnapshot: () => consumer.createSnapshot(),
1025
+ applySnapshot: (snapshot) => consumer.applySnapshot(snapshot),
1026
+ applyMessage: (message) => consumer.applyMessage(message),
1027
+ channel: options?.channel
1028
+ });
1029
+ this.history = this.historyDriver.historySignal;
1030
+ this.messages = this.historyDriver.messagesSignal;
1031
+ this.currentIndex = this.historyDriver.currentIndexSignal;
1032
+ this.keys = signal2([...this.storeKeys]).asReadonly();
27
1033
  trackStore(this);
28
1034
  }
29
1035
  signalsState = /* @__PURE__ */ new Map();
1036
+ storeKeys;
1037
+ historyDriver;
1038
+ /** @inheritDoc */
1039
+ travelTo = (index) => this.historyDriver.travelTo(index);
1040
+ /** @inheritDoc */
1041
+ undo = () => this.historyDriver.undo();
1042
+ /** @inheritDoc */
1043
+ redo = () => this.historyDriver.redo();
1044
+ /** @inheritDoc */
1045
+ getDeadLetters = () => this.historyDriver.getDeadLetters();
1046
+ /** @inheritDoc */
1047
+ replayDeadLetter = (id) => this.historyDriver.replayDeadLetter(id);
1048
+ /** @inheritDoc */
1049
+ replayDeadLetters = () => this.historyDriver.replayDeadLetters();
1050
+ /** @inheritDoc */
1051
+ getCurrentIndex = () => this.historyDriver.getCurrentIndex();
1052
+ /** @inheritDoc */
1053
+ history;
1054
+ /** @inheritDoc */
1055
+ messages;
1056
+ /** @inheritDoc */
1057
+ currentIndex;
1058
+ /** @inheritDoc */
1059
+ keys;
1060
+ replay(idOrIds) {
1061
+ if (Array.isArray(idOrIds)) {
1062
+ return this.historyDriver.replay(idOrIds);
1063
+ }
1064
+ return this.historyDriver.replay(idOrIds);
1065
+ }
1066
+ getHistory(key) {
1067
+ if (key === void 0) {
1068
+ return this.historyDriver.getHistory();
1069
+ }
1070
+ return this.historyDriver.getHistory(key);
1071
+ }
1072
+ getMessages(key) {
1073
+ if (key === void 0) {
1074
+ return this.historyDriver.getMessages();
1075
+ }
1076
+ return this.historyDriver.getMessages(key);
1077
+ }
30
1078
  /**
31
1079
  * Returns a **read-only** `Signal` for the given store slot.
32
1080
  *
@@ -71,23 +1119,13 @@ var BaseStore = class {
71
1119
  * @param newState - Partial state to merge (e.g. `{ data: newData, status: 'Success' }`).
72
1120
  */
73
1121
  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);
1122
+ this.historyDriver.publish(
1123
+ createUpdateMessage(key, cloneValue(newState))
1124
+ );
85
1125
  }
86
1126
  /** Resets every slot in this store to its initial idle state. */
87
1127
  clearAll() {
88
- Object.keys(this.storeEnum).forEach((key) => {
89
- this.clear(key);
90
- });
1128
+ this.historyDriver.publish(createClearAllMessage());
91
1129
  }
92
1130
  /**
93
1131
  * Resets a single slot to `{ data: undefined, isLoading: false, status: undefined, errors: undefined }`.
@@ -95,20 +1133,7 @@ var BaseStore = class {
95
1133
  * @param key - The slot to clear.
96
1134
  */
97
1135
  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);
1136
+ this.historyDriver.publish(createClearMessage(key));
112
1137
  }
113
1138
  /**
114
1139
  * Marks a slot as loading: sets `isLoading: true` and clears `status` and `errors`.
@@ -116,19 +1141,7 @@ var BaseStore = class {
116
1141
  * @param key - The slot to mark as loading.
117
1142
  */
118
1143
  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
- );
1144
+ this.historyDriver.publish(createStartLoadingMessage(key));
132
1145
  }
133
1146
  /**
134
1147
  * Marks a slot as no longer loading: sets `isLoading: false`.
@@ -137,17 +1150,7 @@ var BaseStore = class {
137
1150
  * @param key - The slot to stop loading.
138
1151
  */
139
1152
  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
- );
1153
+ this.historyDriver.publish(createStopLoadingMessage(key));
151
1154
  }
152
1155
  /**
153
1156
  * Merges a single entity into a {@link KeyedResourceData} slot.
@@ -159,36 +1162,13 @@ var BaseStore = class {
159
1162
  * @param entity - The entity value to store.
160
1163
  */
161
1164
  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
- });
1165
+ this.historyDriver.publish(
1166
+ createUpdateKeyedOneMessage(
1167
+ key,
1168
+ resourceKey,
1169
+ cloneValue(entity)
1170
+ )
1171
+ );
192
1172
  }
193
1173
  /**
194
1174
  * Removes a single entity from a {@link KeyedResourceData} slot,
@@ -199,43 +1179,9 @@ var BaseStore = class {
199
1179
  * @param resourceKey - The entity identifier to remove.
200
1180
  */
201
1181
  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
- })
1182
+ this.historyDriver.publish(
1183
+ createClearKeyedOneMessage(key, resourceKey)
236
1184
  );
237
- const updatedState = currentState();
238
- this.notifyUpdateHooks(key, updatedState, previousState);
239
1185
  }
240
1186
  /**
241
1187
  * Marks a single entity within a keyed slot as loading.
@@ -246,47 +1192,9 @@ var BaseStore = class {
246
1192
  * @param resourceKey - The entity identifier to mark as loading.
247
1193
  */
248
1194
  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
- })
1195
+ this.historyDriver.publish(
1196
+ createStartKeyedLoadingMessage(key, resourceKey)
287
1197
  );
288
- const updatedState = currentState();
289
- this.notifyUpdateHooks(key, updatedState, previousState);
290
1198
  }
291
1199
  notifyUpdateHooks(key, nextState, previousState) {
292
1200
  const hooks = updateHooksMap.get(this);
@@ -294,191 +1202,165 @@ var BaseStore = class {
294
1202
  if (!keyHooks) {
295
1203
  return;
296
1204
  }
297
- keyHooks.forEach(
298
- (hook) => hook(
299
- nextState,
300
- previousState
301
- )
302
- );
1205
+ const errors = [];
1206
+ keyHooks.forEach((hook) => {
1207
+ try {
1208
+ hook(
1209
+ nextState,
1210
+ previousState
1211
+ );
1212
+ } catch (error) {
1213
+ errors.push(error);
1214
+ }
1215
+ });
1216
+ if (errors.length > 0) {
1217
+ queueMicrotask(() => {
1218
+ if (errors.length === 1) {
1219
+ throw errors[0];
1220
+ }
1221
+ throw new AggregateError(
1222
+ errors,
1223
+ `${errors.length} onUpdate hooks threw for key "${String(key)}"`
1224
+ );
1225
+ });
1226
+ }
303
1227
  }
304
1228
  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
- };
1229
+ this.storeKeys.forEach((key) => {
313
1230
  this.signalsState.set(
314
- _typedKey,
315
- signal(initialState)
1231
+ key,
1232
+ signal2(createDefaultState())
316
1233
  );
317
1234
  });
318
1235
  }
319
1236
  };
320
1237
 
321
1238
  // src/lazy-store.ts
322
- 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
- }
1239
+ import { signal as signal3 } from "@angular/core";
336
1240
  var LazyStore = class {
337
1241
  signals = /* @__PURE__ */ new Map();
338
1242
  hooks = /* @__PURE__ */ new Map();
339
- constructor() {
1243
+ historyDriver;
1244
+ /** @inheritDoc */
1245
+ travelTo = (index) => this.historyDriver.travelTo(index);
1246
+ /** @inheritDoc */
1247
+ undo = () => this.historyDriver.undo();
1248
+ /** @inheritDoc */
1249
+ redo = () => this.historyDriver.redo();
1250
+ getMessages(key) {
1251
+ if (key === void 0) {
1252
+ return this.historyDriver.getMessages();
1253
+ }
1254
+ return this.historyDriver.getMessages(key);
1255
+ }
1256
+ getDeadLetters = () => this.historyDriver.getDeadLetters();
1257
+ /** @inheritDoc */
1258
+ replayDeadLetter = (id) => this.historyDriver.replayDeadLetter(id);
1259
+ /** @inheritDoc */
1260
+ replayDeadLetters = () => this.historyDriver.replayDeadLetters();
1261
+ /** @inheritDoc */
1262
+ getCurrentIndex = () => this.historyDriver.getCurrentIndex();
1263
+ /** @inheritDoc */
1264
+ history;
1265
+ /** @inheritDoc */
1266
+ messages;
1267
+ /** @inheritDoc */
1268
+ currentIndex;
1269
+ /** @inheritDoc */
1270
+ keys;
1271
+ keysSignal = signal3([]);
1272
+ replay(idOrIds) {
1273
+ if (Array.isArray(idOrIds)) {
1274
+ return this.historyDriver.replay(idOrIds);
1275
+ }
1276
+ return this.historyDriver.replay(idOrIds);
1277
+ }
1278
+ getHistory(key) {
1279
+ if (key === void 0) {
1280
+ return this.historyDriver.getHistory();
1281
+ }
1282
+ return this.historyDriver.getHistory(key);
1283
+ }
1284
+ constructor(options) {
1285
+ const consumer = createStoreMessageConsumer(
1286
+ {
1287
+ getOrCreate: (key) => this.getOrCreate(key),
1288
+ getAllKeys: () => this.signals.keys()
1289
+ },
1290
+ {
1291
+ notify: (key, next, prev) => this.notifyHooks(key, next, prev)
1292
+ }
1293
+ );
1294
+ this.historyDriver = createStoreHistory({
1295
+ captureSnapshot: () => consumer.createSnapshot(),
1296
+ applySnapshot: (snapshot) => consumer.applySnapshot(snapshot),
1297
+ applyMessage: (message) => consumer.applyMessage(message),
1298
+ channel: options?.channel
1299
+ });
1300
+ this.history = this.historyDriver.historySignal;
1301
+ this.messages = this.historyDriver.messagesSignal;
1302
+ this.currentIndex = this.historyDriver.currentIndexSignal;
1303
+ this.keys = this.keysSignal.asReadonly();
340
1304
  trackStore(this);
341
1305
  }
342
1306
  getOrCreate(key) {
343
1307
  let sig = this.signals.get(key);
344
1308
  if (!sig) {
345
- sig = signal2(createDefaultState());
1309
+ sig = signal3(createDefaultState());
346
1310
  this.signals.set(key, sig);
1311
+ this.keysSignal.update((prev) => [...prev, key]);
347
1312
  }
348
1313
  return sig;
349
1314
  }
1315
+ /** @inheritDoc */
350
1316
  get(key) {
351
1317
  return this.getOrCreate(key);
352
1318
  }
1319
+ /** @inheritDoc */
353
1320
  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);
1321
+ this.historyDriver.publish(
1322
+ createUpdateMessage(key, cloneValue(newState))
1323
+ );
359
1324
  }
1325
+ /** @inheritDoc */
360
1326
  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);
1327
+ this.historyDriver.publish(createClearMessage(key));
366
1328
  }
1329
+ /** @inheritDoc */
367
1330
  clearAll() {
368
- for (const key of this.signals.keys()) {
369
- this.clear(key);
370
- }
1331
+ this.historyDriver.publish(createClearAllMessage());
371
1332
  }
1333
+ /** @inheritDoc */
372
1334
  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
- );
1335
+ this.historyDriver.publish(createStartLoadingMessage(key));
382
1336
  }
1337
+ /** @inheritDoc */
383
1338
  stopLoading(key) {
384
- const sig = this.getOrCreate(key);
385
- sig.update(
386
- (state) => ({
387
- ...state,
388
- isLoading: false
389
- })
390
- );
1339
+ this.historyDriver.publish(createStopLoadingMessage(key));
391
1340
  }
1341
+ /** @inheritDoc */
392
1342
  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
- });
1343
+ this.historyDriver.publish(
1344
+ createUpdateKeyedOneMessage(
1345
+ key,
1346
+ resourceKey,
1347
+ cloneValue(entity)
1348
+ )
1349
+ );
411
1350
  }
1351
+ /** @inheritDoc */
412
1352
  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
- })
1353
+ this.historyDriver.publish(
1354
+ createClearKeyedOneMessage(key, resourceKey)
443
1355
  );
444
- const updatedState = sig();
445
- this.notifyHooks(key, updatedState, previousState);
446
1356
  }
1357
+ /** @inheritDoc */
447
1358
  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
- })
1359
+ this.historyDriver.publish(
1360
+ createStartKeyedLoadingMessage(key, resourceKey)
478
1361
  );
479
- const updatedState = sig();
480
- this.notifyHooks(key, updatedState, previousState);
481
1362
  }
1363
+ /** @inheritDoc */
482
1364
  onUpdate(key, callback) {
483
1365
  if (!this.hooks.has(key)) {
484
1366
  this.hooks.set(key, []);
@@ -501,12 +1383,28 @@ var LazyStore = class {
501
1383
  if (!keyHooks) {
502
1384
  return;
503
1385
  }
504
- keyHooks.forEach(
505
- (hook) => hook(
506
- nextState,
507
- previousState
508
- )
509
- );
1386
+ const errors = [];
1387
+ keyHooks.forEach((hook) => {
1388
+ try {
1389
+ hook(
1390
+ nextState,
1391
+ previousState
1392
+ );
1393
+ } catch (error) {
1394
+ errors.push(error);
1395
+ }
1396
+ });
1397
+ if (errors.length > 0) {
1398
+ queueMicrotask(() => {
1399
+ if (errors.length === 1) {
1400
+ throw errors[0];
1401
+ }
1402
+ throw new AggregateError(
1403
+ errors,
1404
+ `${errors.length} onUpdate hooks threw for key "${String(key)}"`
1405
+ );
1406
+ });
1407
+ }
510
1408
  }
511
1409
  };
512
1410
 
@@ -515,12 +1413,12 @@ import { InjectionToken, inject } from "@angular/core";
515
1413
 
516
1414
  // src/dynamic-store.ts
517
1415
  var DynamicStore = class extends BaseStore {
518
- constructor(config) {
1416
+ constructor(config, options) {
519
1417
  const identityEnum = Object.keys(config).reduce(
520
1418
  (acc, key) => ({ ...acc, [key]: key }),
521
1419
  {}
522
1420
  );
523
- super(identityEnum);
1421
+ super(identityEnum, options);
524
1422
  }
525
1423
  };
526
1424
 
@@ -541,12 +1439,12 @@ function mirrorKey(source, sourceKey, target, targetKeyOrOptions, options) {
541
1439
  }
542
1440
 
543
1441
  // src/collect-keyed.ts
544
- import { createKeyedResourceData as createKeyedResourceData3, isAnyKeyLoading as isAnyKeyLoading3 } from "@flurryx/core";
1442
+ import { createKeyedResourceData as createKeyedResourceData2, isAnyKeyLoading as isAnyKeyLoading2 } from "@flurryx/core";
545
1443
  function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
546
1444
  const resolvedTargetKey = typeof targetKeyOrOptions === "string" ? targetKeyOrOptions : sourceKey;
547
1445
  const resolvedOptions = typeof targetKeyOrOptions === "object" ? targetKeyOrOptions : options;
548
1446
  target.update(resolvedTargetKey, {
549
- data: createKeyedResourceData3()
1447
+ data: createKeyedResourceData2()
550
1448
  });
551
1449
  let previousId;
552
1450
  const cleanup = source.onUpdate(sourceKey, (state) => {
@@ -577,7 +1475,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
577
1475
  };
578
1476
  target.update(resolvedTargetKey, {
579
1477
  data: updatedKeyed,
580
- isLoading: isAnyKeyLoading3(newIsLoading),
1478
+ isLoading: isAnyKeyLoading2(newIsLoading),
581
1479
  status: "Success"
582
1480
  });
583
1481
  previousId = currentId;
@@ -599,7 +1497,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
599
1497
  };
600
1498
  target.update(resolvedTargetKey, {
601
1499
  data: updatedKeyed,
602
- isLoading: isAnyKeyLoading3(newIsLoading)
1500
+ isLoading: isAnyKeyLoading2(newIsLoading)
603
1501
  });
604
1502
  previousId = currentId;
605
1503
  } else if (resourceState.data === void 0 && previousId !== void 0) {
@@ -615,7 +1513,7 @@ function collectKeyed(source, sourceKey, target, targetKeyOrOptions, options) {
615
1513
  };
616
1514
  target.update(resolvedTargetKey, {
617
1515
  data: updatedKeyed,
618
- isLoading: isAnyKeyLoading3(remainingLoading)
1516
+ isLoading: isAnyKeyLoading2(remainingLoading)
619
1517
  });
620
1518
  previousId = void 0;
621
1519
  } else if (resourceState.isLoading && currentId !== void 0) {
@@ -655,7 +1553,6 @@ function wireMirrors(store, mirrors) {
655
1553
  def.targetKey
656
1554
  );
657
1555
  }
658
- return store;
659
1556
  }
660
1557
  function wireMirrorKeyed(store, defs) {
661
1558
  for (const def of defs) {
@@ -670,7 +1567,6 @@ function wireMirrorKeyed(store, defs) {
670
1567
  }
671
1568
  );
672
1569
  }
673
- return store;
674
1570
  }
675
1571
  var MIRROR_SELF_SAME_KEY_ERROR = "mirrorSelf source and target keys must be different";
676
1572
  function wireSelfMirrors(store, defs) {
@@ -685,7 +1581,6 @@ function wireSelfMirrors(store, defs) {
685
1581
  def.targetKey
686
1582
  );
687
1583
  }
688
- return store;
689
1584
  }
690
1585
  function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors = []) {
691
1586
  return {
@@ -742,11 +1637,11 @@ function createBuilder(accum, mirrors = [], mirrorKeyedDefs = [], selfMirrors =
742
1637
  selfMirrors
743
1638
  );
744
1639
  },
745
- build() {
1640
+ build(options) {
746
1641
  return new InjectionToken("FlurryxStore", {
747
1642
  providedIn: "root",
748
1643
  factory: () => {
749
- const store = new DynamicStore(accum);
1644
+ const store = new DynamicStore(accum, options);
750
1645
  wireMirrors(store, mirrors);
751
1646
  wireMirrorKeyed(store, mirrorKeyedDefs);
752
1647
  wireSelfMirrors(store, selfMirrors);
@@ -817,11 +1712,11 @@ function createConstrainedBuilder(_enumObj, accum, mirrors = [], mirrorKeyedDefs
817
1712
  selfMirrors
818
1713
  );
819
1714
  },
820
- build() {
1715
+ build(options) {
821
1716
  return new InjectionToken("FlurryxStore", {
822
1717
  providedIn: "root",
823
1718
  factory: () => {
824
- const store = new DynamicStore(accum);
1719
+ const store = new DynamicStore(accum, options);
825
1720
  wireMirrors(store, mirrors);
826
1721
  wireMirrorKeyed(store, mirrorKeyedDefs);
827
1722
  wireSelfMirrors(store, selfMirrors);
@@ -868,11 +1763,11 @@ function createInterfaceBuilder(mirrors = [], mirrorKeyedDefs = [], selfMirrors
868
1763
  selfMirrors
869
1764
  );
870
1765
  },
871
- build() {
1766
+ build(options) {
872
1767
  return new InjectionToken("FlurryxStore", {
873
1768
  providedIn: "root",
874
1769
  factory: () => {
875
- const store = new LazyStore();
1770
+ const store = new LazyStore(options);
876
1771
  wireMirrors(store, mirrors);
877
1772
  wireMirrorKeyed(store, mirrorKeyedDefs);
878
1773
  wireSelfMirrors(store, selfMirrors);
@@ -897,7 +1792,14 @@ export {
897
1792
  LazyStore,
898
1793
  Store,
899
1794
  clearAllStores,
1795
+ cloneValue,
900
1796
  collectKeyed,
1797
+ createCompositeStoreMessageChannel,
1798
+ createInMemoryStoreMessageChannel,
1799
+ createLocalStorageStoreMessageChannel,
1800
+ createSessionStorageStoreMessageChannel,
1801
+ createSnapshotRestorePatch,
1802
+ createStorageStoreMessageChannel,
901
1803
  mirrorKey
902
1804
  };
903
1805
  //# sourceMappingURL=index.js.map