@smthrs/notifications 1.0.0-rc.7 → 1.0.0-rc.9

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/src/Projection.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  import { Projection as JournalProjection } from "@smthrs/journal"
8
8
  import * as Effect from "effect/Effect"
9
9
  import * as Option from "effect/Option"
10
+ import * as Replay from "./internal/replay.ts"
10
11
  import * as NotificationEvent from "./NotificationEvent.ts"
11
12
  import * as NotificationState from "./NotificationState.ts"
12
13
 
@@ -18,7 +19,10 @@ import * as NotificationState from "./NotificationState.ts"
18
19
  * `NotificationState.applyAdmission` applies the decision each record already
19
20
  * committed, and an admitted record is retained however many are pending. A
20
21
  * composition built with `NotificationQueue.layerWith` above the default
21
- * replays every admission it wrote.
22
+ * replays every admission it wrote. A versioned edit of a steer that is still
23
+ * pending replaces that item in place, exactly as the queue's own fold reads
24
+ * it, so an edited steer projects as one pending item rather than one per
25
+ * version.
22
26
  *
23
27
  * The projected `capacity` is `NotificationState.defaultCapacity` because the
24
28
  * initial state has to start somewhere and no journal record carries the bound
@@ -37,9 +41,7 @@ export const derive = JournalProjection.make({
37
41
 
38
42
  const event = decoded.value
39
43
  if (NotificationEvent.isAdmitted(event)) {
40
- return Effect.succeed(
41
- NotificationState.applyAdmission(state, event.notification, entry.seq, event.decision)
42
- )
44
+ return Effect.succeed(Replay.applyAdmitted(state, event, entry.seq))
43
45
  }
44
46
  return Effect.succeed(NotificationState.applyPromoted(state, event.ids))
45
47
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The one replay rule for an admission record, shared by the queue's fold and
3
+ * the journal projection so the two read the same history to the same state.
4
+ *
5
+ * @private
6
+ * @since 1.0.0
7
+ */
8
+
9
+ import type * as NotificationEvent from "../NotificationEvent.ts"
10
+ import * as NotificationState from "../NotificationState.ts"
11
+
12
+ /**
13
+ * Applies one committed admission record to the state so far.
14
+ *
15
+ * A versioned record whose id is still pending is an edit: it replaces that
16
+ * item in place and keeps its original sequence, so the edit is delivered at
17
+ * the cutoff the first version was admitted under. Every other record applies
18
+ * the decision it committed.
19
+ *
20
+ * @private
21
+ * @since 1.0.0
22
+ */
23
+ export const applyAdmitted = (
24
+ state: NotificationState.State,
25
+ event: NotificationEvent.Admitted,
26
+ seq: number
27
+ ): NotificationState.State => {
28
+ if (event.version !== undefined && state.items.some((item) => item.notification.id === event.notification.id)) {
29
+ return Object.freeze({
30
+ ...state,
31
+ items: Object.freeze(state.items.map((item) =>
32
+ item.notification.id === event.notification.id
33
+ ? Object.freeze({ notification: event.notification, seq: item.seq })
34
+ : item
35
+ ))
36
+ })
37
+ }
38
+ return NotificationState.applyAdmission(state, event.notification, seq, event.decision)
39
+ }