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