@flurryx/store 1.0.0 → 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,5 +1,5 @@
1
1
  // src/base-store.ts
2
- import { signal } from "@angular/core";
2
+ import { signal as signal2 } from "@angular/core";
3
3
 
4
4
  // src/store-clone.ts
5
5
  function cloneValue(value) {
@@ -82,6 +82,9 @@ function createSnapshotRestorePatch(currentState, snapshotState) {
82
82
  return patch;
83
83
  }
84
84
 
85
+ // src/store-replay.ts
86
+ import { signal, computed } from "@angular/core";
87
+
85
88
  // src/store-messages.ts
86
89
  var INVALID_HISTORY_INDEX_ERROR = "History index is out of range";
87
90
  var INVALID_HISTORY_MESSAGE_ID_ERROR = "History message id is out of range";
@@ -388,6 +391,125 @@ function toDeadLetterEntry(record) {
388
391
  failedAt: record.lastAttemptedAt ?? record.createdAt
389
392
  };
390
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
+ }
391
513
  function createStoreHistory(config) {
392
514
  const messageChannel = config.channel ?? createInMemoryStoreMessageChannel();
393
515
  const clock = config.clock ?? Date.now;
@@ -401,18 +523,30 @@ function createStoreHistory(config) {
401
523
  }
402
524
  ];
403
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
+ }
404
536
  function recordSnapshot(record) {
405
537
  const nextIndex = history.length;
406
- history = [
407
- ...history,
408
- {
409
- id: record.id,
410
- index: nextIndex,
411
- message: cloneValue(record.message),
412
- snapshot: config.captureSnapshot(),
413
- acknowledgedAt: record.acknowledgedAt
414
- }
415
- ];
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
+ });
416
550
  currentIndex = nextIndex;
417
551
  }
418
552
  function truncateFutureHistory() {
@@ -420,6 +554,9 @@ function createStoreHistory(config) {
420
554
  return;
421
555
  }
422
556
  history = history.slice(0, currentIndex + 1);
557
+ historyCollection = createStableReadonlyCollection(
558
+ historyCollection.slice(0, currentIndex + 1)
559
+ );
423
560
  }
424
561
  function ensureIndexInRange(index) {
425
562
  if (!Number.isInteger(index) || index < 0 || index >= history.length) {
@@ -430,6 +567,7 @@ function createStoreHistory(config) {
430
567
  ensureIndexInRange(index);
431
568
  config.applySnapshot(history[index].snapshot);
432
569
  currentIndex = index;
570
+ notifyVersion();
433
571
  }
434
572
  function undo() {
435
573
  if (currentIndex === 0) {
@@ -462,6 +600,10 @@ function createStoreHistory(config) {
462
600
  error
463
601
  };
464
602
  messageChannel.saveMessage(nextRecord);
603
+ messageCollection = upsertStableReadonlyCollectionItem({
604
+ items: messageCollection,
605
+ item: cloneValue(nextRecord)
606
+ });
465
607
  return nextRecord;
466
608
  }
467
609
  function consumeRecord(record, options) {
@@ -485,6 +627,7 @@ function createStoreHistory(config) {
485
627
  truncateFutureHistory();
486
628
  recordSnapshot(acknowledgedRecord);
487
629
  }
630
+ notifyVersion();
488
631
  return true;
489
632
  } catch (error) {
490
633
  persistMessageAttempt(
@@ -496,6 +639,7 @@ function createStoreHistory(config) {
496
639
  getErrorMessage(error),
497
640
  attemptedAt
498
641
  );
642
+ notifyVersion();
499
643
  return false;
500
644
  }
501
645
  }
@@ -539,9 +683,35 @@ function createStoreHistory(config) {
539
683
  });
540
684
  return acknowledgedCount;
541
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
+ });
542
705
  return {
706
+ historySignal,
707
+ messagesSignal,
708
+ currentIndexSignal,
543
709
  publish(message) {
544
710
  const record = messageChannel.publish(message);
711
+ messageCollection = appendStableReadonlyCollectionItem({
712
+ items: messageCollection,
713
+ item: cloneValue(record)
714
+ });
545
715
  return consumeRecord(record);
546
716
  },
547
717
  replay(input) {
@@ -850,48 +1020,60 @@ var BaseStore = class {
850
1020
  notify: (key, next, prev) => this.notifyUpdateHooks(key, next, prev)
851
1021
  }
852
1022
  );
853
- this.history = createStoreHistory({
1023
+ this.historyDriver = createStoreHistory({
854
1024
  captureSnapshot: () => consumer.createSnapshot(),
855
1025
  applySnapshot: (snapshot) => consumer.applySnapshot(snapshot),
856
1026
  applyMessage: (message) => consumer.applyMessage(message),
857
1027
  channel: options?.channel
858
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();
859
1033
  trackStore(this);
860
1034
  }
861
1035
  signalsState = /* @__PURE__ */ new Map();
862
1036
  storeKeys;
863
- history;
1037
+ historyDriver;
1038
+ /** @inheritDoc */
1039
+ travelTo = (index) => this.historyDriver.travelTo(index);
1040
+ /** @inheritDoc */
1041
+ undo = () => this.historyDriver.undo();
864
1042
  /** @inheritDoc */
865
- travelTo = (index) => this.history.travelTo(index);
1043
+ redo = () => this.historyDriver.redo();
866
1044
  /** @inheritDoc */
867
- undo = () => this.history.undo();
1045
+ getDeadLetters = () => this.historyDriver.getDeadLetters();
868
1046
  /** @inheritDoc */
869
- redo = () => this.history.redo();
1047
+ replayDeadLetter = (id) => this.historyDriver.replayDeadLetter(id);
870
1048
  /** @inheritDoc */
871
- getDeadLetters = () => this.history.getDeadLetters();
1049
+ replayDeadLetters = () => this.historyDriver.replayDeadLetters();
872
1050
  /** @inheritDoc */
873
- replayDeadLetter = (id) => this.history.replayDeadLetter(id);
1051
+ getCurrentIndex = () => this.historyDriver.getCurrentIndex();
874
1052
  /** @inheritDoc */
875
- replayDeadLetters = () => this.history.replayDeadLetters();
1053
+ history;
1054
+ /** @inheritDoc */
1055
+ messages;
1056
+ /** @inheritDoc */
1057
+ currentIndex;
876
1058
  /** @inheritDoc */
877
- getCurrentIndex = () => this.history.getCurrentIndex();
1059
+ keys;
878
1060
  replay(idOrIds) {
879
1061
  if (Array.isArray(idOrIds)) {
880
- return this.history.replay(idOrIds);
1062
+ return this.historyDriver.replay(idOrIds);
881
1063
  }
882
- return this.history.replay(idOrIds);
1064
+ return this.historyDriver.replay(idOrIds);
883
1065
  }
884
1066
  getHistory(key) {
885
1067
  if (key === void 0) {
886
- return this.history.getHistory();
1068
+ return this.historyDriver.getHistory();
887
1069
  }
888
- return this.history.getHistory(key);
1070
+ return this.historyDriver.getHistory(key);
889
1071
  }
890
1072
  getMessages(key) {
891
1073
  if (key === void 0) {
892
- return this.history.getMessages();
1074
+ return this.historyDriver.getMessages();
893
1075
  }
894
- return this.history.getMessages(key);
1076
+ return this.historyDriver.getMessages(key);
895
1077
  }
896
1078
  /**
897
1079
  * Returns a **read-only** `Signal` for the given store slot.
@@ -937,13 +1119,13 @@ var BaseStore = class {
937
1119
  * @param newState - Partial state to merge (e.g. `{ data: newData, status: 'Success' }`).
938
1120
  */
939
1121
  update(key, newState) {
940
- this.history.publish(
1122
+ this.historyDriver.publish(
941
1123
  createUpdateMessage(key, cloneValue(newState))
942
1124
  );
943
1125
  }
944
1126
  /** Resets every slot in this store to its initial idle state. */
945
1127
  clearAll() {
946
- this.history.publish(createClearAllMessage());
1128
+ this.historyDriver.publish(createClearAllMessage());
947
1129
  }
948
1130
  /**
949
1131
  * Resets a single slot to `{ data: undefined, isLoading: false, status: undefined, errors: undefined }`.
@@ -951,7 +1133,7 @@ var BaseStore = class {
951
1133
  * @param key - The slot to clear.
952
1134
  */
953
1135
  clear(key) {
954
- this.history.publish(createClearMessage(key));
1136
+ this.historyDriver.publish(createClearMessage(key));
955
1137
  }
956
1138
  /**
957
1139
  * Marks a slot as loading: sets `isLoading: true` and clears `status` and `errors`.
@@ -959,7 +1141,7 @@ var BaseStore = class {
959
1141
  * @param key - The slot to mark as loading.
960
1142
  */
961
1143
  startLoading(key) {
962
- this.history.publish(createStartLoadingMessage(key));
1144
+ this.historyDriver.publish(createStartLoadingMessage(key));
963
1145
  }
964
1146
  /**
965
1147
  * Marks a slot as no longer loading: sets `isLoading: false`.
@@ -968,7 +1150,7 @@ var BaseStore = class {
968
1150
  * @param key - The slot to stop loading.
969
1151
  */
970
1152
  stopLoading(key) {
971
- this.history.publish(createStopLoadingMessage(key));
1153
+ this.historyDriver.publish(createStopLoadingMessage(key));
972
1154
  }
973
1155
  /**
974
1156
  * Merges a single entity into a {@link KeyedResourceData} slot.
@@ -980,7 +1162,7 @@ var BaseStore = class {
980
1162
  * @param entity - The entity value to store.
981
1163
  */
982
1164
  updateKeyedOne(key, resourceKey, entity) {
983
- this.history.publish(
1165
+ this.historyDriver.publish(
984
1166
  createUpdateKeyedOneMessage(
985
1167
  key,
986
1168
  resourceKey,
@@ -997,7 +1179,7 @@ var BaseStore = class {
997
1179
  * @param resourceKey - The entity identifier to remove.
998
1180
  */
999
1181
  clearKeyedOne(key, resourceKey) {
1000
- this.history.publish(
1182
+ this.historyDriver.publish(
1001
1183
  createClearKeyedOneMessage(key, resourceKey)
1002
1184
  );
1003
1185
  }
@@ -1010,7 +1192,7 @@ var BaseStore = class {
1010
1192
  * @param resourceKey - The entity identifier to mark as loading.
1011
1193
  */
1012
1194
  startKeyedLoading(key, resourceKey) {
1013
- this.history.publish(
1195
+ this.historyDriver.publish(
1014
1196
  createStartKeyedLoadingMessage(key, resourceKey)
1015
1197
  );
1016
1198
  }
@@ -1047,49 +1229,57 @@ var BaseStore = class {
1047
1229
  this.storeKeys.forEach((key) => {
1048
1230
  this.signalsState.set(
1049
1231
  key,
1050
- signal(createDefaultState())
1232
+ signal2(createDefaultState())
1051
1233
  );
1052
1234
  });
1053
1235
  }
1054
1236
  };
1055
1237
 
1056
1238
  // src/lazy-store.ts
1057
- import { signal as signal2 } from "@angular/core";
1239
+ import { signal as signal3 } from "@angular/core";
1058
1240
  var LazyStore = class {
1059
1241
  signals = /* @__PURE__ */ new Map();
1060
1242
  hooks = /* @__PURE__ */ new Map();
1061
- history;
1243
+ historyDriver;
1062
1244
  /** @inheritDoc */
1063
- travelTo = (index) => this.history.travelTo(index);
1245
+ travelTo = (index) => this.historyDriver.travelTo(index);
1064
1246
  /** @inheritDoc */
1065
- undo = () => this.history.undo();
1247
+ undo = () => this.historyDriver.undo();
1066
1248
  /** @inheritDoc */
1067
- redo = () => this.history.redo();
1249
+ redo = () => this.historyDriver.redo();
1068
1250
  getMessages(key) {
1069
1251
  if (key === void 0) {
1070
- return this.history.getMessages();
1252
+ return this.historyDriver.getMessages();
1071
1253
  }
1072
- return this.history.getMessages(key);
1254
+ return this.historyDriver.getMessages(key);
1073
1255
  }
1256
+ getDeadLetters = () => this.historyDriver.getDeadLetters();
1074
1257
  /** @inheritDoc */
1075
- getDeadLetters = () => this.history.getDeadLetters();
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;
1076
1265
  /** @inheritDoc */
1077
- replayDeadLetter = (id) => this.history.replayDeadLetter(id);
1266
+ messages;
1078
1267
  /** @inheritDoc */
1079
- replayDeadLetters = () => this.history.replayDeadLetters();
1268
+ currentIndex;
1080
1269
  /** @inheritDoc */
1081
- getCurrentIndex = () => this.history.getCurrentIndex();
1270
+ keys;
1271
+ keysSignal = signal3([]);
1082
1272
  replay(idOrIds) {
1083
1273
  if (Array.isArray(idOrIds)) {
1084
- return this.history.replay(idOrIds);
1274
+ return this.historyDriver.replay(idOrIds);
1085
1275
  }
1086
- return this.history.replay(idOrIds);
1276
+ return this.historyDriver.replay(idOrIds);
1087
1277
  }
1088
1278
  getHistory(key) {
1089
1279
  if (key === void 0) {
1090
- return this.history.getHistory();
1280
+ return this.historyDriver.getHistory();
1091
1281
  }
1092
- return this.history.getHistory(key);
1282
+ return this.historyDriver.getHistory(key);
1093
1283
  }
1094
1284
  constructor(options) {
1095
1285
  const consumer = createStoreMessageConsumer(
@@ -1101,19 +1291,24 @@ var LazyStore = class {
1101
1291
  notify: (key, next, prev) => this.notifyHooks(key, next, prev)
1102
1292
  }
1103
1293
  );
1104
- this.history = createStoreHistory({
1294
+ this.historyDriver = createStoreHistory({
1105
1295
  captureSnapshot: () => consumer.createSnapshot(),
1106
1296
  applySnapshot: (snapshot) => consumer.applySnapshot(snapshot),
1107
1297
  applyMessage: (message) => consumer.applyMessage(message),
1108
1298
  channel: options?.channel
1109
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();
1110
1304
  trackStore(this);
1111
1305
  }
1112
1306
  getOrCreate(key) {
1113
1307
  let sig = this.signals.get(key);
1114
1308
  if (!sig) {
1115
- sig = signal2(createDefaultState());
1309
+ sig = signal3(createDefaultState());
1116
1310
  this.signals.set(key, sig);
1311
+ this.keysSignal.update((prev) => [...prev, key]);
1117
1312
  }
1118
1313
  return sig;
1119
1314
  }
@@ -1123,29 +1318,29 @@ var LazyStore = class {
1123
1318
  }
1124
1319
  /** @inheritDoc */
1125
1320
  update(key, newState) {
1126
- this.history.publish(
1321
+ this.historyDriver.publish(
1127
1322
  createUpdateMessage(key, cloneValue(newState))
1128
1323
  );
1129
1324
  }
1130
1325
  /** @inheritDoc */
1131
1326
  clear(key) {
1132
- this.history.publish(createClearMessage(key));
1327
+ this.historyDriver.publish(createClearMessage(key));
1133
1328
  }
1134
1329
  /** @inheritDoc */
1135
1330
  clearAll() {
1136
- this.history.publish(createClearAllMessage());
1331
+ this.historyDriver.publish(createClearAllMessage());
1137
1332
  }
1138
1333
  /** @inheritDoc */
1139
1334
  startLoading(key) {
1140
- this.history.publish(createStartLoadingMessage(key));
1335
+ this.historyDriver.publish(createStartLoadingMessage(key));
1141
1336
  }
1142
1337
  /** @inheritDoc */
1143
1338
  stopLoading(key) {
1144
- this.history.publish(createStopLoadingMessage(key));
1339
+ this.historyDriver.publish(createStopLoadingMessage(key));
1145
1340
  }
1146
1341
  /** @inheritDoc */
1147
1342
  updateKeyedOne(key, resourceKey, entity) {
1148
- this.history.publish(
1343
+ this.historyDriver.publish(
1149
1344
  createUpdateKeyedOneMessage(
1150
1345
  key,
1151
1346
  resourceKey,
@@ -1155,13 +1350,13 @@ var LazyStore = class {
1155
1350
  }
1156
1351
  /** @inheritDoc */
1157
1352
  clearKeyedOne(key, resourceKey) {
1158
- this.history.publish(
1353
+ this.historyDriver.publish(
1159
1354
  createClearKeyedOneMessage(key, resourceKey)
1160
1355
  );
1161
1356
  }
1162
1357
  /** @inheritDoc */
1163
1358
  startKeyedLoading(key, resourceKey) {
1164
- this.history.publish(
1359
+ this.historyDriver.publish(
1165
1360
  createStartKeyedLoadingMessage(key, resourceKey)
1166
1361
  );
1167
1362
  }